From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Philip Kaludercic Newsgroups: gmane.emacs.devel Subject: Re: [NonGNU ELPA] New package: devil Date: Wed, 10 May 2023 06:09:58 +0000 Message-ID: <87fs84hg3t.fsf@posteo.net> References: <875y91uc9f.fsf@posteo.net> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="14301"; mail-complaints-to="usenet@ciao.gmane.io" Cc: emacs-devel@gnu.org To: Susam Pal Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Wed May 10 08:11:25 2023 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1pwd2p-0003Sx-M6 for ged-emacs-devel@m.gmane-mx.org; Wed, 10 May 2023 08:11:24 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pwd1p-0007gN-F7; Wed, 10 May 2023 02:10:23 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pwd1f-0007g2-Ki for emacs-devel@gnu.org; Wed, 10 May 2023 02:10:11 -0400 Original-Received: from mout01.posteo.de ([185.67.36.65]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pwd1X-00044O-JQ for emacs-devel@gnu.org; Wed, 10 May 2023 02:10:11 -0400 Original-Received: from submission (posteo.de [185.67.36.169]) by mout01.posteo.de (Postfix) with ESMTPS id 524BA24053D for ; Wed, 10 May 2023 08:09:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1683698999; bh=fYm/pJB+xCcbWPKoEIXfOJMLmv6Iy13VDnU9+reS+GM=; h=From:To:Cc:Subject:Autocrypt:Date:From; b=oQowsI4Hudj+hPBJJBcS4Caw+L2a3ZIzEjT7GFgTeQBA7qgYAbAumNQNLDv7nEpyc 4EmA0OBxAWLCyKXuMPt3s5f8UqAJX6oNkjphqIpkmr4l/sxdo22PvaXAWuuMSLboE5 +1g+g/7lIIAPHzdQevf9hPRnb2GQO6TzTvseIar2mhyX2p4nHWChjqvw8ae+tZxSdt wCXfs/SMy+6tKvNjHprG6uJRSWc3MtfQFFRwxQXzbmxk+oqWOhHLHKwLMHQLmQpp24 DZjl50MD1cM9wGP6wpsmMT+EwlFBnGhskw+d6ogvGAafn9kEkoENovbhQBxeydndik OtRpf7x3v/AMw== Original-Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4QGPkt3Xcpz6tvZ; Wed, 10 May 2023 08:09:58 +0200 (CEST) In-Reply-To: (Susam Pal's message of "Tue, 9 May 2023 21:56:57 +0100") Autocrypt: addr=philipk@posteo.net; keydata= mDMEZBBQQhYJKwYBBAHaRw8BAQdAHJuofBrfqFh12uQu0Yi7mrl525F28eTmwUDflFNmdui0QlBo aWxpcCBLYWx1ZGVyY2ljIChnZW5lcmF0ZWQgYnkgYXV0b2NyeXB0LmVsKSA8cGhpbGlwa0Bwb3N0 ZW8ubmV0PoiWBBMWCAA+FiEEDg7HY17ghYlni8XN8xYDWXahwukFAmQQUEICGwMFCQHhM4AFCwkI BwIGFQoJCAsCBBYCAwECHgECF4AACgkQ8xYDWXahwulikAEA77hloUiSrXgFkUVJhlKBpLCHUjA0 mWZ9j9w5d08+jVwBAK6c4iGP7j+/PhbkxaEKa4V3MzIl7zJkcNNjHCXmvFcEuDgEZBBQQhIKKwYB BAGXVQEFAQEHQI5NLiLRjZy3OfSt1dhCmFyn+fN/QKELUYQetiaoe+MMAwEIB4h+BBgWCAAmFiEE Dg7HY17ghYlni8XN8xYDWXahwukFAmQQUEICGwwFCQHhM4AACgkQ8xYDWXahwukm+wEA8cml4JpK NeAu65rg+auKrPOP6TP/4YWRCTIvuYDm0joBALw98AMz7/qMHvSCeU/hw9PL6u6R2EScxtpKnWof z4oM Received-SPF: pass client-ip=185.67.36.65; envelope-from=philipk@posteo.net; helo=mout01.posteo.de X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.devel:306018 Archived-At: --=-=-= Content-Type: text/plain Susam Pal writes: > Philip Kaludercic wrote: >> Susam Pal writes: >> >> > Hello! >> > >> > I am the author and maintainer of a new package named Devil. This package >> > intercepts keystrokes entered by the user and applies translation rules to >> > translate the keystrokes into Emacs key sequences. It supports three types >> > of rules: special keys that map to custom commands that are invoked >> > immediately prior to any translations, translation rules to translate Devil >> > key sequences to regular Emacs key sequences, and repeatable keys to allow >> > a Devil key sequence to be repeated by typing the last keystroke over and >> > over again using a transient map. >> > >> > See the README at https://github.com/susam/devil for more details. >> >> Looks interesting, here is a diff with a few comments: > > Thanks for the review and the diff! The code looks much better with > these changes. Like we discussed a little while ago in the #emacs > channel, if you send me the patches, I'll apply it to the code. Here are the patches, feel free to modify or leave out whatever you want: --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0001-devil.el-Update-header-with-more-information.patch >From e1757d9005f82624a1db435f53f250291e177519 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Wed, 10 May 2023 07:36:54 +0200 Subject: [PATCH 1/8] * devil.el: Update header with more information --- devil.el | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/devil.el b/devil.el index 7c5d4ca..5bfb848 100644 --- a/devil.el +++ b/devil.el @@ -2,10 +2,11 @@ ;; Copyright (c) 2022-2023 Susam Pal -;; Author: Susam Pal +;; Author: Susam Pal +;; Maintainer: Susam Pal ;; Version: 0.2.0 ;; Package-Requires: ((emacs "24.4")) -;; Keywords: convenience +;; Keywords: convenience, abbrev ;; URL: https://github.com/susam/devil ;; This file is not part of GNU Emacs. -- 2.39.2 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0002-Remove-custom-version-command.patch >From a2d9b22fe60ec8cdf78c69ea873d3d299c4c42d2 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Wed, 10 May 2023 07:38:34 +0200 Subject: [PATCH 2/8] Remove custom version command. * devil.el (devil-version, devil-show-version): Remove. The version of a package can be generically inspected with C-h P devil RET. --- devil.el | 6 ------ 1 file changed, 6 deletions(-) diff --git a/devil.el b/devil.el index 5bfb848..fa08c5f 100644 --- a/devil.el +++ b/devil.el @@ -37,13 +37,7 @@ ;; key sequences without using modifier keys. ;;; Code: -(defconst devil-version "0.2.0" - "Devil version number.") -(defun devil-show-version () - "Show Devil version number in the echo area." - (interactive) - (message "Devil %s" devil-version)) (defvar devil-key "," "The key sequence that begins Devil input. -- 2.39.2 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0003-Add-devil-customisation-group.patch >From 324e6f8655a7cad7ca0c9a15fb1f8a119484b292 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Wed, 10 May 2023 07:49:26 +0200 Subject: [PATCH 3/8] Add 'devil' customisation group * devil.el (devil-key, devil-translations, devil-repeatable-keys, devil-prompt): Convert to user options. (global-devil-mode): Remove unnecessary :group tag. --- devil.el | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/devil.el b/devil.el index fa08c5f..461537e 100644 --- a/devil.el +++ b/devil.el @@ -38,20 +38,26 @@ ;;; Code: +(defgroup devil '() + "Minor mode for Devil-like command entering." ;is there a clearer description? + :prefix "devil-" + :group 'editing) -(defvar devil-key "," +(defcustom devil-key "," "The key sequence that begins Devil input. The key sequence must be specified in the format returned by `C-h k' (`describe-key'). This variable should be set before enabling -Devil mode for it to take effect.") +Devil mode for it to take effect." + :type 'key-sequence) -(defvar devil-lighter " Devil" - "String displayed on the mode line when Devil mode is enabled.") +(defcustom devil-lighter " Devil" + "String displayed on the mode line when Devil mode is enabled." + :type 'string) (defvar devil-mode-map (let ((map (make-sparse-keymap))) - (define-key map (kbd devil-key) #'devil) + (define-key map devil-key #'devil) map) "Keymap to wake up Devil when `devil-key' is typed. @@ -69,15 +75,16 @@ be modified before loading Devil for it to take effect.") ;;;###autoload (define-globalized-minor-mode - global-devil-mode devil-mode devil--on :group 'devil + global-devil-mode devil-mode devil--on (if global-devil-mode (devil-add-extra-keys) (devil-remove-extra-keys))) (defun devil--on () "Turn Devil mode on." (devil-mode 1)) -(defvar devil-logging nil - "Non-nil if and only if Devil should print log messages.") +(defcustom devil-logging nil + "Non-nil if and only if Devil should print log messages." + :type 'boolean) (defvar devil-special-keys (list (cons "%k %k" (lambda () (interactive) (devil-run-key "%k"))) @@ -91,7 +98,7 @@ this alist, the function or lambda in the corresponding value is invoked. The format control specifier `%k' may be used to represent `devil-key' in the keys.") -(defvar devil-translations +(defcustom devil-translations (list (cons "%k z" "C-") (cons "%k %k" "%k") (cons "%k m m" "M-") @@ -106,9 +113,10 @@ The translation rules are applied in the sequence they occur in the alist. For each rule, if the key occurs anywhere in the Devil key sequence, it is replaced with the corresponding value in the translation rule. The format control specifier `%k' may be used -to represent `devil-key' in the keys.") +to represent `devil-key' in the keys." + :type '(alist :key-type string :value-type string)) -(defvar devil-repeatable-keys +(defcustom devil-repeatable-keys (list "%k p" "%k n" "%k f" @@ -124,7 +132,8 @@ to represent `devil-key' in the keys.") The value of this variable is a list where each item represents a key sequence that may be repeated merely by typing the last character in the key sequence. The format control specified `%k' -may be used to represent `devil-key' in the keys.") +may be used to represent `devil-key' in the keys." + :type '(repeat string)) (defun devil-run-key (key) "Execute the given key sequence KEY. @@ -183,14 +192,15 @@ recursively to read yet another key from the user." (unless (devil--run-command key) (devil--read-key key))) -(defvar devil-prompt "Devil: %t" +(defcustom devil-prompt "Devil: %t" "A format control string that determines the Devil prompt. The following format control sequences are supported: %k - Devil key sequence read by Devil so far. %t - Emacs key sequence translated from Devil key sequence read so far. -%% - The percent sign.") +%% - The percent sign." + :type 'string) (defun devil--make-prompt (key) "Create Devil prompt based on the given KEY." -- 2.39.2 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0004-Fix-spacing-in-documentation-strings.patch >From 5896bfa8061709e14a6fd3a94f7039a6b0978d26 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Wed, 10 May 2023 08:00:19 +0200 Subject: [PATCH 4/8] Fix spacing in documentation strings --- devil.el | 54 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/devil.el b/devil.el index 461537e..5f8ecfa 100644 --- a/devil.el +++ b/devil.el @@ -47,7 +47,7 @@ "The key sequence that begins Devil input. The key sequence must be specified in the format returned by `C-h -k' (`describe-key'). This variable should be set before enabling +k' (`describe-key'). This variable should be set before enabling Devil mode for it to take effect." :type 'key-sequence) @@ -62,9 +62,9 @@ Devil mode for it to take effect." "Keymap to wake up Devil when `devil-key' is typed. By default, only `devil-key' is added to this keymap so that -Devil can be activated using it. To support multiple activation +Devil can be activated using it. To support multiple activation keys, this variable may be modified to a new keymap that defines -multiple different keys to activate Devil. This variable should +multiple different keys to activate Devil. This variable should be modified before loading Devil for it to take effect.") ;;;###autoload @@ -93,9 +93,9 @@ be modified before loading Devil for it to take effect.") "Special Devil keys that are executed as soon as they are typed. The value of this variable is an alist where each key represents -a Devil key sequence. If a Devil key sequence matches any key in +a Devil key sequence. If a Devil key sequence matches any key in this alist, the function or lambda in the corresponding value is -invoked. The format control specifier `%k' may be used to +invoked. The format control specifier `%k' may be used to represent `devil-key' in the keys.") (defcustom devil-translations @@ -110,9 +110,9 @@ The value of this variable is an alist where each item represents a translation rule that is applied on the Devil key sequence read from the user to obtain the Emacs key sequence to be executed. The translation rules are applied in the sequence they occur in -the alist. For each rule, if the key occurs anywhere in the Devil +the alist. For each rule, if the key occurs anywhere in the Devil key sequence, it is replaced with the corresponding value in the -translation rule. The format control specifier `%k' may be used +translation rule. The format control specifier `%k' may be used to represent `devil-key' in the keys." :type '(alist :key-type string :value-type string)) @@ -131,7 +131,7 @@ to represent `devil-key' in the keys." The value of this variable is a list where each item represents a key sequence that may be repeated merely by typing the last -character in the key sequence. The format control specified `%k' +character in the key sequence. The format control specified `%k' may be used to represent `devil-key' in the keys." :type '(repeat string)) @@ -182,11 +182,11 @@ translation to Emacs key sequence, or an undefined key sequence after translation to Emacs key sequence. The argument KEY is a vector that represents the key sequence -read so far. This function reads a new key from the user, appends +read so far. This function reads a new key from the user, appends it to KEY, and then checks if the result is a valid key sequence -or an undefined key sequence. If the result is a valid key +or an undefined key sequence. If the result is a valid key sequence for a special key command or an Emacs command, then the -command is executed. Otherwise, this function calls itself +command is executed. Otherwise, this function calls itself recursively to read yet another key from the user." (setq key (vconcat key (vector (read-key (devil--make-prompt key))))) (unless (devil--run-command key) @@ -204,6 +204,8 @@ The following format control sequences are supported: (defun devil--make-prompt (key) "Create Devil prompt based on the given KEY." + ;; If you are interested in adding Compat as a dependency, you can + ;; make use of `format-spec' without raining the minimum version. (let ((result devil-prompt) (controls (list (cons "%k" (key-description key)) (cons "%t" (devil-translate key)) @@ -215,16 +217,16 @@ The following format control sequences are supported: (defun devil--run-command (key) "Try running the command bound to the key sequence in KEY. -KEY is a vector that represents a sequence of keystrokes. If KEY +KEY is a vector that represents a sequence of keystrokes. If KEY is found to be a special key in `devil-special-keys', the corresponding special command is executed immediately and t is returned. Otherwise, it is translated to an Emacs key sequence using -`devil-translations'. If the resulting Emacs key sequence is +`devil-translations'. If the resulting Emacs key sequence is found to be a complete key sequence, the command it is bound to -is executed interactively and t is returned. If it is found to be -an undefined key sequence, then t is returned. If the resulting +is executed interactively and t is returned. If it is found to be +an undefined key sequence, then t is returned. If the resulting Emacs key sequence is found to be an incomplete key sequence, then nil is returned." (devil--log "Trying to execute key: %s" (key-description key)) @@ -236,7 +238,7 @@ then nil is returned." If the given key sequence KEY is found to be a special key in `devil-special-keys', the corresponding special command is -executed, and t is returned. Otherwise nil is returned." +executed, and t is returned. Otherwise nil is returned." (catch 'break (dolist (entry devil-special-keys) (when (string= (key-description key) (devil-format (car entry))) @@ -250,9 +252,9 @@ executed, and t is returned. Otherwise nil is returned." After translating KEY to an Emacs key sequence, if the resulting key sequence turns out to be an incomplete key, then nil is -returned. If it turns out to be a complete key sequence, the -corresponding Emacs command is executed, and t is returned. If it -turns out to be an undefined key sequence, t is returned. The +returned. If it turns out to be a complete key sequence, the +corresponding Emacs command is executed, and t is returned. If it +turns out to be an undefined key sequence, t is returned. The return value t indicates to the caller that no more Devil key sequences should be read from the user." (let* ((described-key (key-description key)) @@ -312,27 +314,27 @@ read so far." "Update variables that maintain command loop information. The given KEY and BINDING is used to update variables that -maintain command loop information. This allows the commands that +maintain command loop information. This allows the commands that depend on them behave as if they were being invoked directly with the original Emacs key sequence." ;; ;; Set `last-command-event' so that `digit-argument' can determine - ;; the correct digit for key sequences like , 5 (C-5). See M-x + ;; the correct digit for key sequences like , 5 (C-5). See M-x ;; find-function RET digit-argument RET for details. (setq last-command-event (aref key (- (length key) 1))) ;; ;; Set `this-command' to make several commands like , z SPC , z SPC - ;; (C-SPC C-SPC) and , p (C-p) work correctly. Emacs copies - ;; `this-command' to `last-command'. Both variables are used by + ;; (C-SPC C-SPC) and , p (C-p) work correctly. Emacs copies + ;; `this-command' to `last-command'. Both variables are used by ;; `set-mark-command' to decide whether to activate/deactivate the - ;; current mark. The first variable is used by vertical motion - ;; commands to keep the cursor at the `temporary-goal-column'. There + ;; current mark. The first variable is used by vertical motion + ;; commands to keep the cursor at the `temporary-goal-column'. There ;; may be other commands too that depend on this variable. (setq this-command binding) ;; ;; Set `real-this-command' to make , x z (C-x z) work correctly. ;; Emacs copies it to `last-repeatable-command' which is then used - ;; by repeat. See the following for more details: + ;; by repeat. See the following for more details: ;; ;; - M-x find-function RET repeat RET ;; - C-h v last-repeatable-command RET -- 2.39.2 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0005-devil.el-devil-saved-keys-Assign-nil-by-default.patch >From 48510c49d7792915826c9cd5930120b62932b9d6 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Wed, 10 May 2023 08:02:43 +0200 Subject: [PATCH 5/8] * devil.el (devil--saved-keys): Assign nil by default Otherwise the variable is assigned a string. --- devil.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devil.el b/devil.el index 5f8ecfa..d67c9da 100644 --- a/devil.el +++ b/devil.el @@ -144,7 +144,7 @@ occurrence `devil-key' is inserted into the buffer." (dolist (key (split-string key)) (if (string= key "%k") (insert devil-key) (execute-kbd-macro (kbd key))))) -(defvar devil--saved-keys +(defvar devil--saved-keys nil ;otherwise `devil--saved-keys' is assigned a string "Original key bindings saved by Devil.") (defun devil-add-extra-keys () -- 2.39.2 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0006-devil.el-devil-log-command-loop-info-Use-a-single-fo.patch >From 303fb740e80348ff6e44dba3c87b49edcd3842ff Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Wed, 10 May 2023 08:03:03 +0200 Subject: [PATCH 6/8] * devil.el (devil--log-command-loop-info): Use a single 'format' --- devil.el | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/devil.el b/devil.el index d67c9da..04229aa 100644 --- a/devil.el +++ b/devil.el @@ -344,11 +344,12 @@ the original Emacs key sequence." (defun devil--log-command-loop-info () "Log command loop information for debugging purpose." (devil--log - (concat "Found " - (format "current-prefix-arg: %s; " current-prefix-arg) - (format "this-command: %s; " this-command) - (format "last-command: %s; " last-command) - (format "last-repeatable-command: %s" last-repeatable-command)))) + (format "Found current-prefix-arg: %s; \ +this-command: %s; last-command: %s; last-repeatable-command: %s" + current-prefix-arg + this-command + last-command + last-repeatable-command))) (defun devil--repeatable-key-p (described-key) "Return t iff DESCRIBED-KEY belongs to `devil-repeatable-keys'." -- 2.39.2 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0007-Move-tests-to-separate-file-using-ERT.patch >From bfbd718aa266244b19c05d98c57d14f1eab41456 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Wed, 10 May 2023 08:05:52 +0200 Subject: [PATCH 7/8] Move tests to separate file using ERT * devil.el: (devil--assert, devil--tests) Remove. * devil-tests.el: Create new file. --- devil-tests.el | 34 ++++++++++++++++++++++++++++++++++ devil.el | 24 ------------------------ 2 files changed, 34 insertions(+), 24 deletions(-) create mode 100644 devil-tests.el diff --git a/devil-tests.el b/devil-tests.el new file mode 100644 index 0000000..22e7071 --- /dev/null +++ b/devil-tests.el @@ -0,0 +1,34 @@ +;;; devil-tests.el --- Tests for devil -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Unit tests for the internal devil logic. Run these with M-x ert +;; RET devil- RET. + +;;; Code: + +(require 'ert) +(require 'devil) + +(ert-deftest devil-invalid-key-p () + "Test if `devil--invalid-key-p' words as expected." + (should (devil--invalid-key-p "")) + (should (devil--invalid-key-p "C-x-C-f")) + (should (devil--invalid-key-p "C-x CC-f")) + (should (not (devil--invalid-key-p "C-x C-f"))) + (should (not (devil--invalid-key-p "C-M-x")))) + +(ert-deftest devil-translate () + "Test if `devil-translate' works as expected." + (should (string= (devil-translate (vconcat ",")) "C-")) + (should (string= (devil-translate (vconcat ",x")) "C-x")) + (should (string= (devil-translate (vconcat ",x,")) "C-x C-")) + (should (string= (devil-translate (vconcat ",x,f")) "C-x C-f")) + (should (string= (devil-translate (vconcat ",,")) ",")) + (should (string= (devil-translate (vconcat ",,,,")) ", ,")) + (should (string= (devil-translate (vconcat ",mx")) "C-M-x")) + (should (string= (devil-translate (vconcat ",mmx")) "M-x")) + (should (string= (devil-translate (vconcat ",mmm")) "M-m"))) + +(provide 'devil-tests) +;;; devil-tests.el ends here diff --git a/devil.el b/devil.el index 04229aa..e037a3c 100644 --- a/devil.el +++ b/devil.el @@ -387,29 +387,5 @@ this-command: %s; last-command: %s; last-repeatable-command: %s" (when devil-logging (apply #'message (concat "Devil: " format-string) args))) -(defmacro devil--assert (form) - "Evaluate FORM and cause error if the result is nil." - `(unless ,form - (error "Assertion failed: %s" ',form))) - -(defun devil--tests () - "Test Devil functions assuming Devil has not been customized." - (devil--assert (devil--invalid-key-p "")) - (devil--assert (devil--invalid-key-p "C-x-C-f")) - (devil--assert (devil--invalid-key-p "C-x CC-f")) - (devil--assert (not (devil--invalid-key-p "C-x C-f"))) - (devil--assert (not (devil--invalid-key-p "C-M-x"))) - (devil--assert (string= (devil-translate (vconcat ",")) "C-")) - (devil--assert (string= (devil-translate (vconcat ",x")) "C-x")) - (devil--assert (string= (devil-translate (vconcat ",x,")) "C-x C-")) - (devil--assert (string= (devil-translate (vconcat ",x,f")) "C-x C-f")) - (devil--assert (string= (devil-translate (vconcat ",,")) ",")) - (devil--assert (string= (devil-translate (vconcat ",,,,")) ", ,")) - (devil--assert (string= (devil-translate (vconcat ",mx")) "C-M-x")) - (devil--assert (string= (devil-translate (vconcat ",mmx")) "M-x")) - (devil--assert (string= (devil-translate (vconcat ",mmm")) "M-m")) - (message "Done")) - (provide 'devil) - ;;; devil.el ends here -- 2.39.2 --=-=-= Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename=0008-Extract-most-of-the-README-into-a-separate-manual.patch Content-Transfer-Encoding: quoted-printable >From f78844cec4a23abe4ebdf4cab82f17b585f524b8 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Wed, 10 May 2023 08:08:21 +0200 Subject: [PATCH 8/8] Extract most of the README into a separate manual --- MANUAL.org | 564 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 594 +---------------------------------------------------- 2 files changed, 565 insertions(+), 593 deletions(-) create mode 100644 MANUAL.org diff --git a/MANUAL.org b/MANUAL.org new file mode 100644 index 0000000..3cd3ca9 --- /dev/null +++ b/MANUAL.org @@ -0,0 +1,564 @@ +#+title: Devil Mode +#+author: Susam Pal +#+email: susam@susam.net +#+language: en +#+options: ':t toc:nil author:t email:t num:t +#+texinfo_dir_category: Emacs misc features +#+texinfo_dir_title: Devil: (devil-mode) +#+texinfo_dir_desc: Minor mode for Devil-like command entering + +#+texinfo: @insertcopying + +Devil mode intercepts our keystrokes and translates them to Emacs key +sequences according to a configurable set of translation rules. For +example, with the default translation rules, when we type =3D, x , f=3D, +Devil translates it to =3DC-x C-f=3D. + +The choice of the comma key (=3D,=3D) to mean the control modifier key +(=3DC-=3D) may seem outrageous. After all, the comma is a very important +punctuation both in prose as well as in code. Can we really get away +with using =3D,=3D to mean the =3DC-=3D modifier? It turns out, this terri= ble +idea can be made to work without too much of a hassle. At least it works +for me! It might work for you too. If it does not, Devil can be +configured to use another key instead of =3D,=3D to mean the =3DC-=3D modi= fier. +See the section [[#custom-devil-key][Custom Devil Key]] for an example. + +A sceptical reader may rightfully ask: If =3D,=3D is translated to =3DC-= =3D, how +on earth are we going to insert a literal =3D,=3D into the text when we ne= ed +to? The section [[#typing-commas][Typing Commas]] answers this. But +before we get there, we have some fundamentals to cover. Take the plunge +and see what unfolds! Maybe you will like this! Maybe you will not! If +you do not like this, you can always retreat to God mode, Evil mode, the +vanilla key bindings, or whatever piques your fancy! + +* Notation +:PROPERTIES: +:CUSTOM_ID: notation +:END: +A quick note about the notation used in the document: The previous +example shows that =3D, x , f=3D is translated to =3DC-x C-f=3D. What this +really means is that the key sequence ,x,f is translated to ctrl+x +ctrl+f. We do not really type any space after the commas. The key , is +directly followed by the key x. However, the key sequence notation used +in this document contains spaces between each keystroke. This is +consistent with how key sequences are represented in Emacs in general +and how Emacs functions like =3Dkey-description=3D, =3Ddescribe-key=3D, et= c. +represent key sequences. When we really need to type a space, it is +represented as =3DSPC=3D. + +* Install +:PROPERTIES: +:CUSTOM_ID: install +:END: +** Install Interactively from MELPA +:PROPERTIES: +:CUSTOM_ID: install-interactively-from-melpa +:END: +Devil is available via [[https://melpa.org/][MELPA]]. You may already +have a preferred way of installing packages from MELPA. If so, install +the package named =3Ddevil=3D to get Devil. For the sake of completeness, +here is a very basic way of installing Devil from MELPA: + +1. Add the following to the Emacs initialization file (i.e., =3D~/.emacs=3D + or =3D~/.emacs.d/init.el=3D or =3D~/.config/emacs/init.el=3D): + + #+begin_src sh + (require 'package) + (add-to-list 'package-archives '("melpa" . "https://melpa.org/package= s/") t) + (package-initialize) + #+end_src + +2. Start Emacs with the updated initialization file and then type these + commands: + + #+begin_example + M-x package-refresh-contents RET + M-x package-install RET devil RET + #+end_example + +3. Confirm that Devil is installed successfully with this command: + + #+begin_example + M-x devil-show-version RET + #+end_example + +4. Enable Devil mode with this command: + + #+begin_example + M-x global-devil-mode RET + #+end_example + +5. Type =3D, x , f=3D and watch Devil translate it to =3DC-x C-f=3D and in= voke + the corresponding command. + +** Install Automatically from MELPA +:PROPERTIES: +:CUSTOM_ID: install-automatically-from-melpa +:END: +Here is yet another basic way to install and enable Devil using Elisp: + +#+begin_example +(require 'package) +(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") = t) +(package-initialize) +(unless package-archive-contents + (package-refresh-contents)) +(unless (package-installed-p 'devil) + (package-install 'devil)) +(global-devil-mode) +(global-set-key (kbd "C-,") 'global-devil-mode) +#+end_example + +Now type =3D, x , f=3D and watch Devil translate it to =3DC-x C-f=3D and i= nvoke +the corresponding command. Type =3DC-,=3D to disable Devil mode. Type =3DC= -,=3D +again to enable it. + +** Install from Git Source +:PROPERTIES: +:CUSTOM_ID: install-from-git-source +:END: +If you prefer obtaining Devil from its Git repository, follow these +steps: + +1. Clone Devil to your system: + + #+begin_src sh + git clone https://github.com/susam/devil.git + #+end_src + +2. Add the following to your Emacs initialization: + + #+begin_example + (add-to-list 'load-path "/path/to/devil/") + (require 'devil) + (global-devil-mode) + (global-set-key (kbd "C-,") 'global-devil-mode) + #+end_example + +3. Start the Emacs editor. Devil mode should now be enabled in all + buffers. The modeline of each buffer should show the =3DDevil=3D lighte= r. + +4. Type =3D, x , f=3D and watch Devil translate it to =3DC-x C-f=3D and in= voke + the corresponding command. Type =3DC-,=3D to disable Devil mode. Type + =3DC-,=3D again to enable it. + +* Use Devil + :PROPERTIES: + :CUSTOM_ID: use-devil + :END: +Assuming vanilla Emacs key bindings have not been changed and Devil has +not been customised, here are some examples that demonstrate how Devil +may be used: + +1. Type =3D, x , f=3D and watch Devil translate it to =3DC-x C-f=3D and in= voke + the find file functionality. + +2. Type =3D, p=3D to move up one line. + +3. To move up multiple lines, type =3D, p p p=3D and so on. Some Devil key + sequences are repeatable keys. The repeatable Devil key sequences can + be repeated by typing the last key of the Devil key sequence over and + over again. + +4. Another example of a repeatable Devil key sequence is =3D, f f f=3D whi= ch + moves the cursor word by multiple characters. A few other examples of + repeatable keys are =3D, k k k=3D to kill lines, =3D, / / /=3D to undo + changs, etc. Type =3DC-h v devil-repeatable-keys RET=3D to see the + complete list of repeatable keys. + +5. Type =3D, s=3D and watch Devil translate it to =3DC-s=3D and invoke + incremental search. + +6. Type =3D, m s=3D and watch Devil translate it to =3DC-M-s=3D and invoke + regular-expression-based incremental search. Yes, =3Dm=3D is translated + to =3DM-=3D. + +7. Type =3D, m m x=3D and watch Devil translate it to =3DM-x=3D and invoke= the + corresponding command. + +8. Type =3D, u , f=3D and watch Devil translate it to =3DC-u C-f=3D and mo= ve the + cursor forward by 4 characters. + +9. Type =3D, u u , f=3D and the cursor moves forward by 16 characters. Dev= il + uses its translation rules and an additional keymap to make the input + key sequence behave like =3DC-u C-u C-f=3D which moves the cursor forwa= rd + by 16 characters. + +10. Type =3D, SPC=3D to type a comma followed by space. This is a special + key sequence to make it convenient to type a comma in the text. Note + that this sacrifices the use of =3D, SPC=3D to mean =3DC-SPC=3D which = could + have been a convenient way to set a mark. + +11. Type =3D, z SPC=3D and watch Devil translate it to =3DC-SPC=3D and set= a + mark. Yes, =3D, z=3D is translated to =3DC-=3D too. + +12. Similarly, type =3D, RET=3D to type a comma followed by the return key. + This is another special key. + +13. Type =3D, ,=3D to type a single comma. This special key is useful for + cases when you really need to type a single literal comma. + +* Typing Commas + :PROPERTIES: + :CUSTOM_ID: typing-commas + :END: +Devil makes the questionable choice of using the comma as its activation +key. As illustrated in the previous section, typing =3D, x , f=3D produces +the same effect as typing =3DC-x C-f=3D. One might naturally wonder how th= en +we are supposed to type literal commas. + +Most often when we edit text, we do not really type a comma in +isolation. Often we immediately follow the comma with a space or a +newline. This assumption usually holds good while editing regular text. +However, this assumption may not hold in some situations, like while +working with code when we need to add a single comma at the end of an +existing line. + +In scenarios where the above assumption holds good, typing =3D, SPC=3D +inserts a comma and a space. Similarly, typing =3D, RET=3D inserts a comma +and a newline. + +In scenarios, when we do need to type a single comma, type =3D, ,=3D +instead. + +Also, it is worth mentioning here that if all this fiddling with the +comma key feels clumsy, we could always customise the Devil key to +something else that feels better. We could also disable Devil mode +temporarily and enable it again later with =3DC-,=3D as explained in secti= on +[[#use-devil][Use Devil]]. + +* Devil Reader + :PROPERTIES: + :CUSTOM_ID: devil-reader + :END: +The following points briefly describe how Devil reads Devil key +sequences, translates them to Emacs key sequences, and runs commands +bound to the key sequences: + +1. As soon as the Devil key is typed (which is =3D,=3D by default), Devil + wakes up and starts reading Devil key sequences. Type + =3DC-h v devil-key RET=3D to see the current Devil key. + +2. After each keystroke is read, Devil checks if the key sequence + accumulated is a special key. If it is, then the special command + bound to the special key is executed immediately. Note that this step + is performed before any translation rules are applied to the input + key sequence. This is how the Devil special key sequence =3D, SPC=3D + inserts a comma and a space. Type =3DC-h v devil-special-keys RET=3D + to see the list of special keys and the commands bound to them. + +3. If the key sequence accumulated so far is not a special key, then + Devil translates the Devil key sequence to a regular Emacs key + sequence. If the regular Emacs key sequence turns out to be a + complete key sequence and some command is found to be bound to it, + then that command is executed immediately. This is how the Devil key + sequence =3D, x , f=3D is translated to =3DC-x C-f=3D and the correspon= ding + binding is executed. If the translated key sequence is a complete key + sequence but no command is bound to it, then Devil displays a message + that the key sequence is undefined. Type + =3DC-h v devil-translations RET=3D to see the list of translation rules. + +4. After successfully translating a Devil key sequence to an Emacs key + sequence and executing the command bound to it, Devil checks if the + key sequence is a repeatable key sequence. If it is found to be a + repeatable key sequence, then Devil sets a transient map so that the + command can be repeated merely by typing the last keystroke of the + input key sequence. This is how =3D, p p p=3D moves the cursor up by + three lines. Type =3DC-h v devil-repeatable-keys RET=3D to see the + list of repeatable Devil key sequences. + +The variables =3Ddevil-special-keys=3D, =3Ddevil-translations=3D, and +=3Ddevil-repeatable-keys=3D may contain keys or values with the string =3D= %k=3D +in them. This is a placeholder for =3Ddevil-key=3D. While applying the +special keys, translation rules, or repeat rules, each =3D%k=3D is replaced +with the actual value of =3Ddevil-key=3D before applying the rules. + +* Translation Rules + :PROPERTIES: + :CUSTOM_ID: translation-rules + :END: +The following points provide an account of the translation rules that +Devil follows in order to convert a Devil key sequence entered by the +user to an Emacs key sequence: + +1. The input key vector read from the user is converted to a key + description (i.e., the string functions like =3Ddescribe-key=3D, + =3Dkey-description=3D, produce). For example, if the user types ,x,f, it + is converted to =3D, x , f=3D. + +2. Now the resulting key description is translated with simple string + replacements. If any part of the string matches a key in + =3Ddevil-translations=3D, then it is replaced with the corresponding + value. For example, =3D, x , f=3D is translated to =3DC- x C- f=3D. Then + Devil normalises the result to =3DC-x C-f=3D by removing superfluous + spaces after the modifier keys. + +3. However, if the simple string based replacement leads to an invalid + Emacs key sequence, it skips the replacement that causes the + resulting Emacs key sequence to become invalid. For example + =3D, m ,=3D results in =3DC-M-C-=3D after the simple string replace= ment + because the default translation rules replace =3D,=3D with =3DC-=3D and= =3Dm=3D + with =3DM-=3D. However, =3DC-M-C-=3D is an invalid key sequence, so the + replacement of the second =3D,=3D to =3DC-=3D is skipped. Therefore, the + input =3D, m ,=3D is translated to =3DC-M-,=3D instead. + +* Translation Examples + :PROPERTIES: + :CUSTOM_ID: translation-examples + :END: +By default, Devil supports a small but peculiar set of translation rules +that can be used to avoid modifier keys while typing various types of +key sequences. See =3DC-h v devil-translations RET=3D for the translation +rules. Here are some examples that demonstrate the default translation +rules. The obvious ones are shown first first. The more peculiar +translations come later in the table. + +| Input | Translated | Remarks | +|-----------+------------+-----------------------------------| +| =3D, s=3D | =3DC-s=3D | =3D,=3D is replaced with =3DC-=3D = | +| =3D, m s=3D | =3DC-M-s=3D | =3Dm=3D is replaced with =3DM-=3D = | +| =3D, z s=3D | =3DC-SPC=3D | =3D, z=3D is replaced with =3DC-=3D too= | +| =3D, z z=3D | =3DC-z=3D | ditto | +| =3D, m m x=3D | =3DM-x=3D | =3D, m m=3D is replaced with =3DM-=3D t= oo | +| =3D, c , ,=3D | =3DC-c ,=3D | =3D, ,=3D is replaced with =3D,=3D = | + +Note how we cannot use =3D, SPC=3D to set a mark because that key sequence +is already reserved as a special key sequence in =3Ddevil-special-keys=3D, +so Devil translates =3D, z=3D to =3DC-=3D too, so that we can still type = =3DC-SPC=3D +using =3D, z s=3D and set a mark. + +Also, note how the translation of =3D, m m=3D to =3DM-=3D allows us to ent= er a +key sequence that begins with the =3DM-=3D modifier key. + +* Bonus Key Bindings + :PROPERTIES: + :CUSTOM_ID: bonus-key-bindings + :END: +Devil adds the following additional key bindings only when Devil is +enabled globally with =3Dglobal-devil-mode=3D: + +- Adds the Devil key to =3Disearch-mode-map=3D, so that Devil key sequences + work in incremental search too. + +- Adds =3Du=3D to =3Duniversal-argument-more=3D to allow repeating the uni= versal + argument command =3DC-u=3D simply by repeating =3Du=3D. + +As mentioned before these features are available only when Devil is +enabled globally with =3Dglobal-devil-mode=3D. If Devil is enabled locally +with =3Ddevil-mode=3D, then these features are not available. + +* Custom Configuration Examples + :PROPERTIES: + :CUSTOM_ID: custom-configuration-examples + :END: +In the examples presented below, the =3D(require 'devil)=3D calls may be +omitted if Devil has been installed from MELPA. There are appropriate +autoloads in place in the Devil package that would ensure that it is +loaded automatically on enabling Devil mode. However, the =3Drequire=3D +calls have been included in the examples below for the sake of +completeness. + +** Local Mode + :PROPERTIES: + :CUSTOM_ID: local-mode + :END: +While the section [[#use-devil][Use Devil]] shows how we enable Devil mode= globally, +this section shows how we can enable it locally. Here is an example +initialization code that enables Devil locally only in text buffers. + +#+begin_example +(require 'devil) +(add-hook 'text-mode-hook 'devil-mode) +(global-set-key (kbd "C-,") 'devil-mode) +#+end_example + +This is not recommended though because this does not provide a seamless +Devil experience. For example, with Devil enabled locally in a text +buffer like this, although we can type =3D, x , f=3D to launch the find-fi= le +minibuffer, we cannot use Devil key sequences in the minibuffer. Further +the special keymaps described in the previous section work only when +Devil is enabled globally. + +** Custom Appearance + :PROPERTIES: + :CUSTOM_ID: custom-appearance + :END: +The following initialization code shows how we can customise Devil to +show a Devil smiley (=F0=9F=98=88) in the modeline and the echo area. + +#+begin_example +(require 'devil) +(setq devil-lighter " \U0001F608") +(setq devil-prompt "\U0001F608 %t") +(global-devil-mode) +(global-set-key (kbd "C-,") 'global-devil-mode) +#+end_example + +This is how Emacs may look if emojis are rendered correctly: + +[[https://i.imgur.com/oYtwnGi.png][[[https://i.imgur.com/oYtwnGi.png]]]] + +** Custom Devil Key + :PROPERTIES: + :CUSTOM_ID: custom-devil-key + :END: +The following initialization code shows how we can customise Devil to +use a different Devil key. + +#+begin_example +(defvar devil-key "") +(defvar devil-special-keys '(("%k %k" . (lambda () (interactive) (devil-ru= n-key "%k"))))) +(require 'devil) +(global-devil-mode) +(global-set-key (kbd "C-") 'global-devil-mode) +#+end_example + +The above example sets the Devil key to the left arrow key, perhaps +another dubious choice for the Devil key. With this configuration, we +can use =3D x f=3D and have Devil translate it to =3DC-x C-f= =3D. + +Additionally, the above example defines the =3Ddevil-special-keys=3D +variable to have a single entry that allows typing =3D =3D to +produce the same effect as the original =3D=3D. It removes the other +entries, so that =3D SPC=3D is no longer reserved as a special key. +Thus =3D SPC=3D can now be used to set a mark like one would normally +expect. + +** Multiple Devil Keys + :PROPERTIES: + :CUSTOM_ID: multiple-devil-keys + :END: +While this package provides the comma (=3D,=3D) as the default and the only +Devil key, nothing stops you from extending the mode map to support +multiple Devil keys. Say, you decide that in addition to activating +Devil with =3D,=3D which also plays the role of =3DC-=3D, you also want to +activate Devil with =3D.=3D which must now play the role of =3DM-=3D. To a= chieve +such a result, you could use this initialization code as a starting +point and then customise it further based on your requirements: + +#+begin_example +(defvar devil-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd ",") #'devil) + (define-key map (kbd ".") #'devil) + map)) +(defvar devil-special-keys '((", ," . (lambda () (insert ","))) + (". ." . (lambda () (insert "."))))) +(defvar devil-translations '(("," . "C-") + ("." . "M-"))) +(require 'devil) +(global-devil-mode) +#+end_example + +With this configuration, we can type =3D, x , f=3D for =3DC-x C-f=3D like +before. But now we can also type =3D. x=3D for =3DM-x=3D. Similarly, we ca= n type +=3D, . s=3D for =3DC-M-s=3D and so on. Further, =3D, ,=3D inserts a litera= l comma +and =3D. .=3D inserts a literal dot. + +Note that by default Devil configures only one activation key (=3D,=3D) +because the more activation keys we add, the more intrusive Devil +becomes during regular editing tasks. Every key that we reserve for +activating Devil loses its default function and then we need workarounds +to somehow invoke the default function associated with that key (like +repeating =3D.=3D twice to insert a single =3D.=3D in the above example). +Therefore, it is a good idea to keep the number of Devil keys as small +as possible. + +* Why? + :PROPERTIES: + :CUSTOM_ID: why + :END: +Why go to the trouble of creating and using something like this? Why not +just remap caps lock to ctrl like every other sane person does? Or if it +is so important to avoid modifier keys, why not use something like God +mode or Evil mode? + +Well, for one, both God mode and Evil mode are modal editing modes. +Devil, on the other hand, provides a modeless editing experience of +Emacs as possible. + +Devil mode began as a fun little tiny experiment. From the outset, it +was clear that using something as crucial as the comma for specifying +the modifier key is asking for trouble. However, I still wanted to see +how far I could go with it. It turned out that in a matter of days, I +was using it full-time for all of my Emacs usage. + +This experiment was partly motivated by Macbook keyboards which do not +have a right ctrl key. Being a touch-typist myself, I found it +inconvenient to type key combinations like =3DC-x=3D, =3DC-a=3D, =3DC-w=3D= , =3DC-s=3D, +etc. where both the modifier key and the modified key need to be pressed +with the left hand fingers. I am not particularly fond of remapping caps +lock to behave like ctrl because that still suffers from the problem +that key combinations like =3DC-x=3D, =3DC-a=3D require pressing both the +modifier key and the modified key with the left hand fingers. I know +many people remap both their caps lock and enter to behave like ctrl. +While I think that is a fine solution, I was not willing to put up with +the work required to make that work seamlessly across all the various +operating systems I work on. + +What began as a tiny whimsical experiment a few years ago turned out to +be quite effective, at least to me. I like that this solution is +implemented purely as Elisp and therefore does not have any external +dependency. I am sharing this solution here in the form of a minor mode, +just in case, there is someone out there who might find this useful too. + +* Comparison with God Mode + :PROPERTIES: + :CUSTOM_ID: comparison-with-god-mode + :END: +God mode provides a modal editing experience but Devil does not. Devil +has the same underlying philosophy as that of God mode, i.e., the user +should not have to learn new key bindings. However, Devil does not have +a hard separation between insert mode and command mode like God mode +has. Instead, Devil waits for an activation key (=3D,=3D by default) and as +soon as it is activated, it intercepts and translates keys, runs the +corresponding command, and then gets out of the way. So Devil tries to +retain the modeless editing experience of vanilla Emacs as much as +possible. + +Now it is worth mentioning that some of this modeless editing experience +can be reproduced in god-mode too using its +=3Dgod-execute-with-current-bindings=3D function. Here is an example: + +#+begin_example +(global-set-key (kbd ",") #'god-execute-with-current-bindings) +#+end_example + +With this configuration, God mode translates =3D, x f=3D to =3DC-x C-f=3D. +Similarly =3D, g x=3D invokes =3DM-x=3D and =3D, G s=3D invokes =3DC-M-x= =3D. This +provides a modeless editing experience in God mode too. However, this +experience does not extend seamlessly to minibuffers. Devil does extend +its Devil key translation to minibuffers. + +Further note that in God mode the ctrl modifier has sticky behaviour, +i.e., the modifier remains active automatically for the entire key +sequence. Therefore in the above example, we type =3D,=3D only once while +typing =3D, x f=3D to invoke =3DC-x C-f=3D. However, this sticky behaviour +implies that we need some way to disambiguate between key sequences like +=3DC-x C-o=3D (delete blank lines) and =3DC-x o=3D (other window). God mode +solves this by introducing =3DSPC=3D to deactivate the modifier, e.g., +=3D, x o=3D translates to =3DC-x C-o=3D but =3D, x SPC o=3D translates to = =3DC-x o=3D. +Devil does not treat the modifier key as sticky which leads to simpler +key sequences at the cost of a little additional typing, i.e., =3D, x , o= =3D +translates to =3DC-x C-o=3D and =3D, x o=3D translates to =3DC-x o=3D. + +To summarize, there are primarily three things that Devil does +differently: + +- Provide a modeless editing experience from the outset. +- Seamlessly extend the same editing experience to minibuffer, + incremental search, etc. +- Translate key sequences using string replacements. This allows for + arbitrary and sophisticated key translations for the adventurous. +- Choose non-sticky behaviour for the modifier keys. + +These differences could make Devil easier to use than God mode for some +people but clumsy for other people. It depends on one's tastes and +preferences. + +* Support + :PROPERTIES: + :CUSTOM_ID: support + :END: +To report bugs, suggest improvements, or ask questions, +[[https://github.com/susam/devil/issues][create issues]]. diff --git a/README.md b/README.md index 749508c..39ef57e 100644 --- a/README.md +++ b/README.md @@ -13,599 +13,7 @@ charm the Devil! But beware, for in this sinister domai= n, you must relinquish your comma key and embrace an editing experience that whispers wicked secrets into your fingertips! =20 - -Contents --------- - -* [Introduction](#introduction) -* [Notation](#notation) -* [Install](#install) - * [Install Interactively from MELPA](#install-interactively-from-melpa) - * [Install Automatically from MELPA](#install-automatically-from-melpa) - * [Install from Git Source](#install-from-git-source) -* [Use Devil](#use-devil) -* [Typing Commas](#typing-commas) -* [Devil Reader](#devil-reader) -* [Translation Rules](#translation-rules) -* [Translation Examples](#translation-examples) -* [Bonus Key Bindings](#bonus-key-bindings) -* [Custom Configuration Examples](#custom-configuration-examples) - * [Local Mode](#local-mode) - * [Custom Appearance](#custom-appearance) - * [Custom Devil Key](#custom-devil-key) - * [Multiple Devil Keys](#multiple-devil-keys) -* [Why?](#why) -* [Comparison with God Mode](#comparison-with-god-mode) -* [Support](#support) -* [Channels](#channels) -* [More](#more) - - -Introduction ------------- - -Devil mode intercepts our keystrokes and translates them to Emacs key -sequences according to a configurable set of translation rules. For -example, with the default translation rules, when we type `, x , f`, -Devil translates it to `C-x C-f`. - -The choice of the comma key (`,`) to mean the control modifier key -(`C-`) may seem outrageous. After all, the comma is a very important -punctuation both in prose as well as in code. Can we really get away -with using `,` to mean the `C-` modifier? It turns out, this terrible -idea can be made to work without too much of a hassle. At least it -works for me! It might work for you too. If it does not, Devil can be -configured to use another key instead of `,` to mean the `C-` -modifier. See the section [Custom Devil Key](#custom-devil-key) for an -example. - -A sceptical reader may rightfully ask: If `,` is translated to `C-`, -how on earth are we going to insert a literal `,` into the text when -we need to? The section [Typing Commas](#typing-commas) answers this. -But before we get there, we have some fundamentals to cover. Take the -plunge and see what unfolds! Maybe you will like this! Maybe you will -not! If you do not like this, you can always retreat to God mode, Evil -mode, the vanilla key bindings, or whatever piques your fancy! - - -Notation --------- - -A quick note about the notation used in the document: The previous -example shows that `, x , f` is translated to `C-x C-f`. What this -really means is that the key sequence -,x,f is translated to -ctrl+x ctrl+f. We do not -really type any space after the commas. The key , is -directly followed by the key x. However, the key sequence -notation used in this document contains spaces between each keystroke. -This is consistent with how key sequences are represented in Emacs in -general and how Emacs functions like `key-description`, -`describe-key`, etc. represent key sequences. When we really need to -type a space, it is represented as `SPC`. - - -Install -------- - -### Install Interactively from MELPA - -Devil is available via [MELPA](https://melpa.org/). You may already -have a preferred way of installing packages from MELPA. If so, install -the package named `devil` to get Devil. For the sake of completeness, -here is a very basic way of installing Devil from MELPA: - - 1. Add the following to the Emacs initialization file (i.e., - `~/.emacs` or `~/.emacs.d/init.el` or `~/.config/emacs/init.el`): - - ```sh - (require 'package) - (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages= /") t) - (package-initialize) - ``` - - 2. Start Emacs with the updated initialization file and then type - these commands: - - ``` - M-x package-refresh-contents RET - M-x package-install RET devil RET - ``` - - 3. Confirm that Devil is installed successfully with this command: - - ``` - M-x devil-show-version RET - ``` - - 4. Enable Devil mode with this command: - - ``` - M-x global-devil-mode RET - ``` - - 4. Type `, x , f` and watch Devil translate it to `C-x C-f` and - invoke the corresponding command. - - -### Install Automatically from MELPA - -Here is yet another basic way to install and enable Devil using Elisp: - -```elisp -(require 'package) -(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") = t) -(package-initialize) -(unless package-archive-contents - (package-refresh-contents)) -(unless (package-installed-p 'devil) - (package-install 'devil)) -(global-devil-mode) -(global-set-key (kbd "C-,") 'global-devil-mode) -``` - -Now type `, x , f` and watch Devil translate it to `C-x C-f` and -invoke the corresponding command. Type `C-,` to disable Devil mode. -Type `C-,` again to enable it. - - -### Install from Git Source - -If you prefer obtaining Devil from its Git repository, follow these -steps: - - 1. Clone Devil to your system: - - ```sh - git clone https://github.com/susam/devil.git - ``` - - 2. Add the following to your Emacs initialization: - - ```elisp - (add-to-list 'load-path "/path/to/devil/") - (require 'devil) - (global-devil-mode) - (global-set-key (kbd "C-,") 'global-devil-mode) - ``` - - 3. Start the Emacs editor. Devil mode should now be enabled in all - buffers. The modeline of each buffer should show the `Devil` - lighter. - - 4. Type `, x , f` and watch Devil translate it to `C-x C-f` and - invoke the corresponding command. Type `C-,` to disable Devil - mode. Type `C-,` again to enable it. - - -Use Devil ---------- - -Assuming vanilla Emacs key bindings have not been changed and Devil -has not been customised, here are some examples that demonstrate how -Devil may be used: - - 1. Type `, x , f` and watch Devil translate it to `C-x C-f` and - invoke the find file functionality. - - 2. Type `, p` to move up one line. - - 3. To move up multiple lines, type `, p p p` and so on. Some Devil - key sequences are repeatable keys. The repeatable Devil key - sequences can be repeated by typing the last key of the Devil key - sequence over and over again. - - 4. Another example of a repeatable Devil key sequence is `, f f f` - which moves the cursor word by multiple characters. A few other - examples of repeatable keys are `, k k k` to kill lines, `, / / /` - to undo changs, etc. Type `C-h v devil-repeatable-keys RET` to see - the complete list of repeatable keys. - - 5. Type `, s` and watch Devil translate it to `C-s` and invoke - incremental search. - - 6. Type `, m s` and watch Devil translate it to `C-M-s` and invoke - regular-expression-based incremental search. Yes, `m` is - translated to `M-`. - - 7. Type `, m m x` and watch Devil translate it to `M-x` and invoke - the corresponding command. - - 8. Type `, u , f` and watch Devil translate it to `C-u C-f` and move - the cursor forward by 4 characters. - - 9. Type `, u u , f` and the cursor moves forward by 16 characters. - Devil uses its translation rules and an additional keymap to make - the input key sequence behave like `C-u C-u C-f` which moves the - cursor forward by 16 characters. - -10. Type `, SPC` to type a comma followed by space. This is a special - key sequence to make it convenient to type a comma in the text. - Note that this sacrifices the use of `, SPC` to mean `C-SPC` which - could have been a convenient way to set a mark. - -11. Type `, z SPC` and watch Devil translate it to `C-SPC` and set a - mark. Yes, `, z` is translated to `C-` too. - -12. Similarly, type `, RET` to type a comma followed by the return - key. This is another special key. - -13. Type `, ,` to type a single comma. This special key is useful for - cases when you really need to type a single literal comma. - - -Typing Commas -------------- - -Devil makes the questionable choice of using the comma as its -activation key. As illustrated in the previous section, typing -`, x , f` produces the same effect as typing `C-x C-f`. One might -naturally wonder how then we are supposed to type literal commas. - -Most often when we edit text, we do not really type a comma in -isolation. Often we immediately follow the comma with a space or a -newline. This assumption usually holds good while editing regular -text. However, this assumption may not hold in some situations, like -while working with code when we need to add a single comma at the end -of an existing line. - -In scenarios where the above assumption holds good, typing `, SPC` -inserts a comma and a space. Similarly, typing `, RET` inserts a comma -and a newline. - -In scenarios, when we do need to type a single comma, type `, ,` instead. - -Also, it is worth mentioning here that if all this fiddling with the -comma key feels clumsy, we could always customise the Devil key to -something else that feels better. We could also disable Devil mode -temporarily and enable it again later with `C-,` as explained in -section [Get Started](#get-started). - - -Devil Reader ------------- - -The following points briefly describe how Devil reads Devil key -sequences, translates them to Emacs key sequences, and runs commands -bound to the key sequences: - - 1. As soon as the Devil key is typed (which is `,` by default), Devil - wakes up and starts reading Devil key sequences. Type `C-h v - devil-key RET` to see the current Devil key. - - 2. After each keystroke is read, Devil checks if the key sequence - accumulated is a special key. If it is, then the special command - bound to the special key is executed immediately. Note that this - step is performed before any translation rules are applied to the - input key sequence. This is how the Devil special key sequence `, - SPC` inserts a comma and a space. Type `C-h v - devil-special-keys RET` to see the list of special keys and - the commands bound to them. - - 3. If the key sequence accumulated so far is not a special key, then - Devil translates the Devil key sequence to a regular Emacs key - sequence. If the regular Emacs key sequence turns out to be a - complete key sequence and some command is found to be bound to it, - then that command is executed immediately. This is how the Devil - key sequence `, x , f` is translated to `C-x C-f` and the - corresponding binding is executed. If the translated key sequence - is a complete key sequence but no command is bound to it, then - Devil displays a message that the key sequence is undefined. Type - `C-h v devil-translations RET` to see the list of translation - rules. - - 4. After successfully translating a Devil key sequence to an Emacs - key sequence and executing the command bound to it, Devil checks - if the key sequence is a repeatable key sequence. If it is found - to be a repeatable key sequence, then Devil sets a transient map - so that the command can be repeated merely by typing the last - keystroke of the input key sequence. This is how `, p p p` moves - the cursor up by three lines. Type `C-h v devil-repeatable-keys - RET` to see the list of repeatable Devil key sequences. - -The variables `devil-special-keys`, `devil-translations`, and -`devil-repeatable-keys` may contain keys or values with the string -`%k` in them. This is a placeholder for `devil-key`. While applying -the special keys, translation rules, or repeat rules, each `%k` is -replaced with the actual value of `devil-key` before applying the -rules. - - -Translation Rules ------------------ - -The following points provide an account of the translation rules that -Devil follows in order to convert a Devil key sequence entered by the -user to an Emacs key sequence: - - 1. The input key vector read from the user is converted to a key - description (i.e., the string functions like `describe-key`, - `key-description`, produce). For example, if the user types - ,x,f, it is converted - to `, x , f`. - - 2. Now the resulting key description is translated with simple string - replacements. If any part of the string matches a key in - `devil-translations`, then it is replaced with the corresponding - value. For example, `, x , f` is translated to `C- x C- f`. Then - Devil normalises the result to `C-x C-f` by removing superfluous - spaces after the modifier keys. - - 3. However, if the simple string based replacement leads to an - invalid Emacs key sequence, it skips the replacement that causes - the resulting Emacs key sequence to become invalid. For example `, - m ,` results in `C-M-C-` after the simple string replacement - because the default translation rules replace `,` with `C-` and - `m` with `M-`. However, `C-M-C-` is an invalid key sequence, so - the replacement of the second `,` to `C-` is skipped. Therefore, - the input `, m ,` is translated to `C-M-,` instead. - - -Translation Examples --------------------- - -By default, Devil supports a small but peculiar set of translation -rules that can be used to avoid modifier keys while typing various -types of key sequences. See `C-h v devil-translations RET` for the -translation rules. Here are some examples that demonstrate the default -translation rules. The obvious ones are shown first first. The more -peculiar translations come later in the table. - -| Input | Translated | Remarks | -|-----------|------------|------------------------------------| -| `, s` | `C-s` | `,` is replaced with `C-` | -| `, m s` | `C-M-s` | `m` is replaced with `M-` | -| `, z s` | `C-SPC` | `, z` is replaced with `C-` too | -| `, z z` | `C-z` | ditto | -| `, m m x` | `M-x` | `, m m` is replaced with `M-` too | -| `, c , ,` | `C-c ,` | `, ,` is replaced with `,` | - -Note how we cannot use `, SPC` to set a mark because that key sequence -is already reserved as a special key sequence in `devil-special-keys`, -so Devil translates `, z` to `C-` too, so that we can still type -`C-SPC` using `, z s` and set a mark. - -Also, note how the translation of `, m m` to `M-` allows us to enter a -key sequence that begins with the `M-` modifier key. - - -Bonus Key Bindings ------------------- - -Devil adds the following additional key bindings only when Devil is -enabled globally with `global-devil-mode`: - -- Adds the Devil key to `isearch-mode-map`, so that Devil key - sequences work in incremental search too. - -- Adds `u` to `universal-argument-more` to allow repeating the - universal argument command `C-u` simply by repeating `u`. - -As mentioned before these features are available only when Devil is -enabled globally with `global-devil-mode`. If Devil is enabled locally -with `devil-mode`, then these features are not available. - - -Custom Configuration Examples ------------------------------ - -In the examples presented below, the `(require 'devil)` calls may be -omitted if Devil has been installed from MELPA. There are appropriate -autoloads in place in the Devil package that would ensure that it is -loaded automatically on enabling Devil mode. However, the `require` -calls have been included in the examples below for the sake of -completeness. - - -### Local Mode - -While the section [Get Started](#get-started) shows how we enable -Devil mode globally, this section shows how we can enable it locally. -Here is an example initialization code that enables Devil locally only -in text buffers. - -```elisp -(require 'devil) -(add-hook 'text-mode-hook 'devil-mode) -(global-set-key (kbd "C-,") 'devil-mode) -``` - -This is not recommended though because this does not provide a -seamless Devil experience. For example, with Devil enabled locally in -a text buffer like this, although we can type `, x , f` to launch the -find-file minibuffer, we cannot use Devil key sequences in the -minibuffer. Further the special keymaps described in the previous -section work only when Devil is enabled globally. - - -### Custom Appearance - -The following initialization code shows how we can customise Devil to -show a Devil smiley (=F0=9F=98=88) in the modeline and the echo area. - -```elisp -(require 'devil) -(setq devil-lighter " \U0001F608") -(setq devil-prompt "\U0001F608 %t") -(global-devil-mode) -(global-set-key (kbd "C-,") 'global-devil-mode) -``` - -This is how Emacs may look if emojis are rendered correctly: - -[![Screenshot of Emacs with Devil smiley][smiley-screenshot]][smiley-scree= nshot] - -[smiley-screenshot]: https://i.imgur.com/oYtwnGi.png - - -### Custom Devil Key - -The following initialization code shows how we can customise Devil to -use a different Devil key. - -```elisp -(defvar devil-key "") -(defvar devil-special-keys '(("%k %k" . (lambda () (interactive) (devil-ru= n-key "%k"))))) -(require 'devil) -(global-devil-mode) -(global-set-key (kbd "C-") 'global-devil-mode) -``` - -The above example sets the Devil key to the left arrow key, perhaps -another dubious choice for the Devil key. With this configuration, we -can use ` x f` and have Devil translate it to `C-x C-f`. - -Additionally, the above example defines the `devil-special-keys` -variable to have a single entry that allows typing ` ` to -produce the same effect as the original ``. It removes the other -entries, so that ` SPC` is no longer reserved as a special key. -Thus ` SPC` can now be used to set a mark like one would -normally expect. - - -### Multiple Devil Keys - -While this package provides the comma (`,`) as the default and the -only Devil key, nothing stops you from extending the mode map to -support multiple Devil keys. Say, you decide that in addition to -activating Devil with `,` which also plays the role of `C-`, you also -want to activate Devil with `.` which must now play the role of `M-`. -To achieve such a result, you could use this initialization code as a -starting point and then customise it further based on your -requirements: - -```elisp -(defvar devil-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd ",") #'devil) - (define-key map (kbd ".") #'devil) - map)) -(defvar devil-special-keys '((", ," . (lambda () (insert ","))) - (". ." . (lambda () (insert "."))))) -(defvar devil-translations '(("," . "C-") - ("." . "M-"))) -(require 'devil) -(global-devil-mode) -``` - -With this configuration, we can type `, x , f` for `C-x C-f` like -before. But now we can also type `. x` for `M-x`. Similarly, we can -type `, . s` for `C-M-s` and so on. Further, `, ,` inserts a literal -comma and `. .` inserts a literal dot. - -Note that by default Devil configures only one activation key (`,`) -because the more activation keys we add, the more intrusive Devil -becomes during regular editing tasks. Every key that we reserve for -activating Devil loses its default function and then we need -workarounds to somehow invoke the default function associated with -that key (like repeating `.` twice to insert a single `.` in the above -example). Therefore, it is a good idea to keep the number of Devil -keys as small as possible. - - -Why? ----- - -Why go to the trouble of creating and using something like this? Why -not just remap caps lock to ctrl like every -other sane person does? Or if it is so important to avoid modifier -keys, why not use something like God mode or Evil mode? - -Well, for one, both God mode and Evil mode are modal editing modes. -Devil, on the other hand, provides a modeless editing experience of -Emacs as possible. - -Devil mode began as a fun little tiny experiment. From the outset, it -was clear that using something as crucial as the comma for specifying -the modifier key is asking for trouble. However, I still wanted to see -how far I could go with it. It turned out that in a matter of days, I -was using it full-time for all of my Emacs usage. - -This experiment was partly motivated by Macbook keyboards which do not -have a right ctrl key. Being a touch-typist myself, I found -it inconvenient to type key combinations like `C-x`, `C-a`, `C-w`, -`C-s`, etc. where both the modifier key and the modified key need to -be pressed with the left hand fingers. I am not particularly fond of -remapping caps lock to behave like ctrl because -that still suffers from the problem that key combinations like `C-x`, -`C-a` require pressing both the modifier key and the modified key with -the left hand fingers. I know many people remap both their caps -lock and enter to behave like ctrl. While -I think that is a fine solution, I was not willing to put up with the -work required to make that work seamlessly across all the various -operating systems I work on. - -What began as a tiny whimsical experiment a few years ago turned out -to be quite effective, at least to me. I like that this solution is -implemented purely as Elisp and therefore does not have any external -dependency. I am sharing this solution here in the form of a minor -mode, just in case, there is someone out there who might find this -useful too. - - -Comparison with God Mode ------------------------- - -God mode provides a modal editing experience but Devil does not. Devil -has the same underlying philosophy as that of God mode, i.e., the user -should not have to learn new key bindings. However, Devil does not -have a hard separation between insert mode and command mode like God -mode has. Instead, Devil waits for an activation key (`,` by default) -and as soon as it is activated, it intercepts and translates keys, -runs the corresponding command, and then gets out of the way. So Devil -tries to retain the modeless editing experience of vanilla Emacs as -much as possible. - -Now it is worth mentioning that some of this modeless editing -experience can be reproduced in god-mode too using its -`god-execute-with-current-bindings` function. Here is an example: - -```elisp -(global-set-key (kbd ",") #'god-execute-with-current-bindings) -``` - -With this configuration, God mode translates `, x f` to `C-x C-f`. -Similarly `, g x` invokes `M-x` and `, G s` invokes `C-M-x`. This -provides a modeless editing experience in God mode too. However, this -experience does not extend seamlessly to minibuffers. Devil does -extend its Devil key translation to minibuffers. - -Further note that in God mode the ctrl modifier has sticky -behaviour, i.e., the modifier remains active automatically for the -entire key sequence. Therefore in the above example, we type `,` only -once while typing `, x f` to invoke `C-x C-f`. However, this sticky -behaviour implies that we need some way to disambiguate between key -sequences like `C-x C-o` (delete blank lines) and `C-x o` (other -window). God mode solves this by introducing `SPC` to deactivate the -modifier, e.g., `, x o` translates to `C-x C-o` but `, x SPC o` -translates to `C-x o`. Devil does not treat the modifier key as sticky -which leads to simpler key sequences at the cost of a little -additional typing, i.e., `, x , o` translates to `C-x C-o` and `, x o` -translates to `C-x o`. - -To summarize, there are primarily three things that Devil does -differently: - - - Provide a modeless editing experience from the outset. - - Seamlessly extend the same editing experience to minibuffer, - incremental search, etc. - - Translate key sequences using string replacements. This allows for - arbitrary and sophisticated key translations for the adventurous. - - Choose non-sticky behaviour for the modifier keys. - -These differences could make Devil easier to use than God mode for -some people but clumsy for other people. It depends on one's tastes -and preferences. - - -Support -------- - -To report bugs, suggest improvements, or ask questions, -[create issues][ISSUES]. - -[ISSUES]: https://github.com/susam/devil/issues - +Read the [manual](./MANUAL.org) for more details on how to use Devil. =20 Channels -------- --=20 2.39.2 --=-=-= Content-Type: text/plain I'll add the package to NonGNU ELPA in a day, unless there are any objections? --=-=-=--