diff --git a/evil-common.el b/evil-common.el index 7f0bad4e..301d97b9 100644 --- a/evil-common.el +++ b/evil-common.el @@ -2990,13 +2990,25 @@ 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))) + (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)) + (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)) properties)))) diff --git a/evil-tests.el b/evil-tests.el index 91492f04..80f3a174 100644 --- a/evil-tests.el +++ b/evil-tests.el @@ -508,7 +508,12 @@ and the beginning") (should (string= (evil-describe 1 2 'exclusive) "1 character")) (should (string= (evil-describe 5 2 'exclusive) - "3 characters")))))) + "3 characters"))) + (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" @@ -533,7 +538,15 @@ and the beginning") (should (string= (evil-describe 1 1 'inclusive) "1 character")) (should (string= (evil-describe 5 2 'inclusive) - "4 characters"))))) + "4 characters"))) + (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" 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