From 523f740e5acad3e47ad9be0f3914347de0c37eac Mon Sep 17 00:00:00 2001 From: Justin Burkett Date: Mon, 22 Aug 2022 10:20:19 -0400 Subject: [PATCH 1/6] Make evil-range aware of composed characters A composed character is a way of visually replacing a string of characters with a single glyph. If beg in the call to evil-range lies in this hidden sequence, adjust the position to the beginning of the sequence. Similarly, make sure end comes at the end of any such hidden sequence. The purpose is to prevent evil from unintentionally modifying part of a hidden sequence of characters. --- evil-common.el | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/evil-common.el b/evil-common.el index 7f0bad4e..913db3d5 100644 --- a/evil-common.el +++ b/evil-common.el @@ -2990,11 +2990,18 @@ If no description is available, return the empty string." (defun evil-range (beg end &optional type &rest properties) "Return a list (BEG END [TYPE] PROPERTIES...). -BEG and END are buffer positions (numbers or markers), -TYPE is a type as per `evil-type-p', and PROPERTIES is -a property list." +BEG and END are buffer positions (numbers or markers), TYPE is a +type as per `evil-type-p', and PROPERTIES is a property list. If +beg or end are inside a sequence of composed characters, adjust +the positions to be outside of this sequence." (let ((beg (evil-normalize-position beg)) (end (evil-normalize-position end))) + (when-let* ((comp (find-composition beg)) + (valid (nth 2 comp))) + (setq beg (nth 0 comp))) + (when-let* ((comp (find-composition end)) + (valid (nth 2 comp))) + (setq end (nth 1 comp))) (when (and (numberp beg) (numberp end)) (append (list (min beg end) (max beg end)) (when (evil-type-p type) From f831ff52d393681c3d1220cb8d46c540688ecbba Mon Sep 17 00:00:00 2001 From: Justin Burkett Date: Mon, 22 Aug 2022 10:32:03 -0400 Subject: [PATCH 2/6] Add tests for handling of composed characters --- evil-tests.el | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/evil-tests.el b/evil-tests.el index 91492f04..dfe81285 100644 --- a/evil-tests.el +++ b/evil-tests.el @@ -508,7 +508,11 @@ and the beginning") (should (string= (evil-describe 1 2 'exclusive) "1 character")) (should (string= (evil-describe 5 2 'exclusive) - "3 characters")))))) + "3 characters"))) + (compose-region 9 15 "ϐ") ; visually replaces "buffer" with ϐ + (ert-info ("Adjust range to account for composed characters") + (should (equal (evil-normalize 10 10 'exclusive) + '(9 15 exclusive))))))) (ert-deftest evil-test-inclusive-type () "Expand and contract the `inclusive' type" @@ -533,7 +537,14 @@ and the beginning") (should (string= (evil-describe 1 1 'inclusive) "1 character")) (should (string= (evil-describe 5 2 'inclusive) - "4 characters"))))) + "4 characters"))) + (compose-region 9 15 "ϐ") ; visually replaces "buffer" with ϐ + (ert-info ("Don't end in composed characters") + (should (equal (evil-expand 7 12 'inclusive) + '(7 15 inclusive :expanded t)))) + (ert-info ("Don't begin in composed characters") + (should (equal (evil-expand 10 20 'inclusive) + '(9 21 inclusive :expanded t)))))) (ert-deftest evil-test-line-type () "Expand the `line' type" From 9de9a3c0d4cb6f6d71b0ad3dc5ace80680af7f2e Mon Sep 17 00:00:00 2001 From: Justin Burkett Date: Mon, 22 Aug 2022 11:39:08 -0400 Subject: [PATCH 3/6] Remove when-let* for older versions of emacs --- evil-common.el | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/evil-common.el b/evil-common.el index 913db3d5..9d0d55b5 100644 --- a/evil-common.el +++ b/evil-common.el @@ -2996,12 +2996,12 @@ beg or end are inside a sequence of composed characters, adjust the positions to be outside of this sequence." (let ((beg (evil-normalize-position beg)) (end (evil-normalize-position end))) - (when-let* ((comp (find-composition beg)) - (valid (nth 2 comp))) - (setq beg (nth 0 comp))) - (when-let* ((comp (find-composition end)) - (valid (nth 2 comp))) - (setq end (nth 1 comp))) + (let ((comp (find-composition beg))) + (when (and (listp comp) (nth 2 comp)) ; valid composition + (setq beg (nth 0 comp)))) + (let ((comp (find-composition end))) + (when (and (listp comp) (nth 2 comp)) + (setq end (nth 1 comp)))) (when (and (numberp beg) (numberp end)) (append (list (min beg end) (max beg end)) (when (evil-type-p type) From ca27fcf3aa36d0f7ddc26164520287baa949583c Mon Sep 17 00:00:00 2001 From: Justin Burkett Date: Mon, 22 Aug 2022 12:06:51 -0400 Subject: [PATCH 4/6] Improve evil-range Account for beg > end case and case where beg or end are nil --- evil-common.el | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/evil-common.el b/evil-common.el index 9d0d55b5..daef2fde 100644 --- a/evil-common.el +++ b/evil-common.el @@ -2995,15 +2995,19 @@ type as per `evil-type-p', and PROPERTIES is a property list. If beg or end are inside a sequence of composed characters, adjust the positions to be outside of this sequence." (let ((beg (evil-normalize-position beg)) - (end (evil-normalize-position end))) - (let ((comp (find-composition beg))) - (when (and (listp comp) (nth 2 comp)) ; valid composition - (setq beg (nth 0 comp)))) - (let ((comp (find-composition end))) - (when (and (listp comp) (nth 2 comp)) - (setq end (nth 1 comp)))) + (end (evil-normalize-position end)) + beg-out end-out) (when (and (numberp beg) (numberp end)) - (append (list (min beg end) (max beg end)) + (setq beg-out (min beg end)) + (setq end-out (max beg end)) + (let ((comp (find-composition beg-out))) + ;; find-composition returns (FROM TO VALID-P) + (when (and (listp comp) (nth 2 comp)) + (setq beg-out (nth 0 comp)))) + (let ((comp (find-composition end-out))) + (when (and (listp comp) (nth 2 comp)) + (setq end-out (nth 1 comp)))) + (append (list beg-out end-out) (when (evil-type-p type) (list type)) properties)))) From a7def96ce59e6e7c1c37cffb3e5c39d24f7d1635 Mon Sep 17 00:00:00 2001 From: Justin Burkett Date: Mon, 22 Aug 2022 20:39:53 -0400 Subject: [PATCH 5/6] Add evil-treat-composed-chars-as-one option Allows user to disable special handling of composed characters. See the docstring for more information. --- evil-common.el | 15 ++++++++------- evil-vars.el | 13 +++++++++++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/evil-common.el b/evil-common.el index daef2fde..301d97b9 100644 --- a/evil-common.el +++ b/evil-common.el @@ -3000,13 +3000,14 @@ the positions to be outside of this sequence." (when (and (numberp beg) (numberp end)) (setq beg-out (min beg end)) (setq end-out (max beg end)) - (let ((comp (find-composition beg-out))) - ;; find-composition returns (FROM TO VALID-P) - (when (and (listp comp) (nth 2 comp)) - (setq beg-out (nth 0 comp)))) - (let ((comp (find-composition end-out))) - (when (and (listp comp) (nth 2 comp)) - (setq end-out (nth 1 comp)))) + (when evil-treat-composed-chars-as-one + (let ((comp (find-composition beg-out))) + ;; find-composition returns (FROM TO VALID-P) + (when (and (listp comp) (nth 2 comp)) + (setq beg-out (nth 0 comp)))) + (let ((comp (find-composition end-out))) + (when (and (listp comp) (nth 2 comp)) + (setq end-out (nth 1 comp))))) (append (list beg-out end-out) (when (evil-type-p type) (list type)) diff --git a/evil-vars.el b/evil-vars.el index b01ee0aa..3f697966 100644 --- a/evil-vars.el +++ b/evil-vars.el @@ -1104,6 +1104,19 @@ without having to introduce new niche functionality. Prefer to set `evil-v$-excludes-newline' to non-nil." "1.15.0") +(defcustom evil-treat-composed-chars-as-one nil + "EXPERIMENTAL. Treat composed characters as single characters. + +Composed characters are sequences of characters in the buffer +that are displayed as a single glyph. This is the device used by +`prettify-symbols-mode' among others. This option tells evil that +these sequences of characters should be treated as a single unit +to, for example, prevent part of the sequence from being deleted +by an evil command. It alters low-level functionality of evil and +is considered experimental." + :type 'boolean + :group 'evil) + (defcustom evil-v$-excludes-newline nil "If non-nil, `evil-end-of-line' does not move as far as to include the `\n' char at eol. This makes `v$' consistent with `$' used as a From 9b133c00161f1c81b2e7e24e459408c64f5177f6 Mon Sep 17 00:00:00 2001 From: Justin Burkett Date: Mon, 22 Aug 2022 21:01:58 -0400 Subject: [PATCH 6/6] Use new variable in tests --- evil-tests.el | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/evil-tests.el b/evil-tests.el index dfe81285..80f3a174 100644 --- a/evil-tests.el +++ b/evil-tests.el @@ -509,10 +509,11 @@ and the beginning") "1 character")) (should (string= (evil-describe 5 2 'exclusive) "3 characters"))) - (compose-region 9 15 "ϐ") ; visually replaces "buffer" with ϐ - (ert-info ("Adjust range to account for composed characters") - (should (equal (evil-normalize 10 10 'exclusive) - '(9 15 exclusive))))))) + (let ((evil-treat-composed-chars-as-one t)) + (compose-region 9 15 "ϐ") ; visually replaces "buffer" with ϐ + (ert-info ("Adjust range to account for composed characters") + (should (equal (evil-normalize 10 10 'exclusive) + '(9 15 exclusive)))))))) (ert-deftest evil-test-inclusive-type () "Expand and contract the `inclusive' type" @@ -538,13 +539,14 @@ and the beginning") "1 character")) (should (string= (evil-describe 5 2 'inclusive) "4 characters"))) - (compose-region 9 15 "ϐ") ; visually replaces "buffer" with ϐ - (ert-info ("Don't end in composed characters") - (should (equal (evil-expand 7 12 'inclusive) - '(7 15 inclusive :expanded t)))) - (ert-info ("Don't begin in composed characters") - (should (equal (evil-expand 10 20 'inclusive) - '(9 21 inclusive :expanded t)))))) + (let ((evil-treat-composed-chars-as-one t)) + (compose-region 9 15 "ϐ") ; visually replaces "buffer" with ϐ + (ert-info ("Don't end in composed characters") + (should (equal (evil-expand 7 12 'inclusive) + '(7 15 inclusive :expanded t)))) + (ert-info ("Don't begin in composed characters") + (should (equal (evil-expand 10 20 'inclusive) + '(9 21 inclusive :expanded t))))))) (ert-deftest evil-test-line-type () "Expand the `line' type"