branch: elpa/recomplete commit 5aa74e66e6d876fa19272a1b1f407cc8dbb2feb7 Author: Campbell Barton <ideasma...@gmail.com> Commit: Campbell Barton <ideasma...@gmail.com>
Use replace in region function that avoids redundant work --- Makefile | 4 ++ recomplete.el | 65 ++++++++++++++++++++---- tests/recomplete-test.el | 127 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..c4f26c62f7 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +test: + @emacs -batch -l tests/recomplete-test.el -f ert-run-tests-batch-and-exit diff --git a/recomplete.el b/recomplete.el index 1a5763808e..773d49a5ba 100644 --- a/recomplete.el +++ b/recomplete.el @@ -67,8 +67,6 @@ ;; after `recomplete-with-callback', so we know not to break the chain in that case. (defvar-local recomplete--alist nil "Internal properties for repeated `recomplete' calls.") -;; --------------------------------------------------------------------------- -;; Generic Functions/Macros (defmacro recomplete--with-advice (fn-orig where fn-advice &rest body) "Execute BODY with advice added. @@ -222,9 +220,7 @@ Argument FN-CACHE stores the result for reuse." (when result-choices (let ((word-at-index (nth (mod cycle-index (length result-choices)) result-choices))) - (goto-char word-beg) - (delete-region word-beg word-end) - (insert word-at-index))) + (recomplete-replace-in-region word-at-index word-beg word-end))) (list result-choices fn-cache))) @@ -275,9 +271,7 @@ Argument FN-CACHE stores the result for reuse." (setq fn-cache (list result-choices word-beg word-end)))) (let ((word-at-index (nth (mod cycle-index (length result-choices)) result-choices))) - (goto-char word-beg) - (delete-region word-beg word-end) - (insert word-at-index)) + (recomplete-replace-in-region word-at-index word-beg word-end)) (list result-choices fn-cache))) @@ -321,9 +315,7 @@ Argument FN-CACHE stores the result for reuse." (setq fn-cache (list result-choices word-beg word-end))))) (let ((word-at-index (nth (mod cycle-index (length result-choices)) result-choices))) - (goto-char word-beg) - (delete-region word-beg word-end) - (insert word-at-index)) + (recomplete-replace-in-region word-at-index word-beg word-end)) (list result-choices fn-cache))) @@ -331,6 +323,57 @@ Argument FN-CACHE stores the result for reuse." ;; --------------------------------------------------------------------------- ;; Public Functions +;;;###autoload +(defun recomplete-replace-in-region (str beg end) + "Utility to replace region from BEG to END with STR. +Return the region replaced." + (let + ( + (len (length str)) + (i-beg nil) + (i-end nil) + (i-end-ofs nil)) + + ;; Check for skip end. + (let ((i 0)) + (let ((len-test (min (- end beg) len))) + (while (< i len-test) + (let ((i-next (1+ i))) + (cond + ((eq (aref str (- len i-next)) (char-after (- end i-next))) + (setq i i-next)) + (t ;; Break. + (setq len-test i)))))) + (unless (zerop i) + (setq i-end (- len i)) + (setq len (- len i)) + (setq end (- end i)) + (setq i-end-ofs i))) + + ;; Check for skip start. + (let ((i 0)) + (let ((len-test (min (- end beg) len))) + (while (< i len-test) + (cond + ((eq (aref str i) (char-after (+ beg i))) + (setq i (1+ i))) + (t ;; Break. + (setq len-test i))))) + (unless (zerop i) + (setq i-beg i) + (setq beg (+ beg i)))) + + (when (or i-beg i-end) + (setq str (substring str (or i-beg 0) (or i-end len)))) + + (goto-char beg) + (delete-region beg end) + (insert str) + (when i-end-ofs + ;; Leave the cursor where it would be if the end wasn't clipped. + (goto-char (+ (point) i-end-ofs))) + (cons beg (+ beg (length str))))) + ;; Make public since users may want to add their own callbacks. ;;;###autoload diff --git a/tests/recomplete-test.el b/tests/recomplete-test.el new file mode 100644 index 0000000000..720e744a1b --- /dev/null +++ b/tests/recomplete-test.el @@ -0,0 +1,127 @@ +;;; recomplete-test.el --- Highlight indent scope test -*- lexical-binding: t -*- + +;; SPDX-License-Identifier: GPL-3.0-or-later +;; Copyright (C) 2022 Campbell Barton + +;; Author: Campbell Barton <ideasma...@gmail.com> + +;; URL: https://codeberg.org/ideasman42/emacs-recomplete +;; Keywords: convenience +;; Version: 0.1 +;; Package-Requires: ((emacs "26.1")) + +;;; Commentary: + +;; This is a test for `recomplete'. +;; + +;;; Usage + +;; +;; To test this file run: +;; +;; `emacs -batch -l tests/recomplete-test.el -f ert-run-tests-batch-and-exit' +;; + +;;; Code: + +(require 'ert) + +;; --------------------------------------------------------------------------- +;; Setup Environment + +(setq recomplete-basedir (concat (file-name-directory load-file-name) "..")) +(add-to-list 'load-path recomplete-basedir) +(require 'recomplete) + +;; --------------------------------------------------------------------------- +;; Test Internal Replacement Function + +(defmacro ert-replace-in-region (id str-src args output replaced-range) + ` + (ert-deftest ,id () + "Generic test." + (with-temp-buffer + (insert ,str-src) + (let ((result (apply 'recomplete-replace-in-region ,args))) + (should (equal ,output (buffer-substring-no-properties (point-min) (point-max)))) + (should (equal result ,replaced-range)))))) + +(ert-replace-in-region replace-in-region-nop "" '("" 1 1) "" (cons 1 1)) +(ert-replace-in-region replace-in-region-single-same "x" '("x" 1 2) "x" (cons 1 1)) +(ert-replace-in-region replace-in-region-single-different "x" '("y" 1 2) "y" (cons 1 2)) + +(ert-replace-in-region replace-in-region-word-same "hello" '("hello" 1 6) "hello" (cons 1 1)) +(ert-replace-in-region replace-in-region-word-same-start "HELLO" '("Hello" 1 6) "Hello" (cons 2 6)) +(ert-replace-in-region replace-in-region-word-same-end "HELLO" '("hELLO" 1 6) "hELLO" (cons 1 2)) +(ert-replace-in-region + replace-in-region-word-different-1 + "hello" + '("world" 1 6) + "world" + (cons 1 6)) +(ert-replace-in-region + replace-in-region-word-different-2 + "hello" + '("HELLO" 1 6) + "HELLO" + (cons 1 6)) +(ert-replace-in-region + replace-in-region-word-contract + "commpletion" + '("completion" 1 12) + "completion" + (cons 3 3)) +(ert-replace-in-region + replace-in-region-word-expand + "copletion" + '("completion" 1 10) + "completion" + (cons 3 4)) + +(ert-replace-in-region + replace-in-region-word-mix-same + "pre hello post" + '("hello" 5 10) + "pre hello post" + (cons 5 5)) +(ert-replace-in-region + replace-in-region-word-mix-same-start + "pre HELLO post" + '("Hello" 5 10) + "pre Hello post" + (cons 6 10)) +(ert-replace-in-region + replace-in-region-word-mix-same-end + "pre HELLO post" + '("hELLO" 5 10) + "pre hELLO post" + (cons 5 6)) +(ert-replace-in-region + replace-in-region-word-mix-different-1 + "pre hello post" + '("world" 5 10) + "pre world post" + (cons 5 10)) +(ert-replace-in-region + replace-in-region-word-mix-different-2 + "pre hello post" + '("HELLO" 5 10) + "pre HELLO post" + (cons 5 10)) +(ert-replace-in-region + replace-in-region-word-mix-contract + "pre commpletion post" + '("completion" 5 16) + "pre completion post" + (cons 7 7)) +(ert-replace-in-region + replace-in-region-word-mix-expand + "pre copletion post" + '("completion" 5 14) + "pre completion post" + (cons 7 8)) + + +(provide 'recomplete-test) +;;; recomplete-test.el ends here