From 25b98b940cdd360c445db74a5816513c5cf034e0 Mon Sep 17 00:00:00 2001 From: Andrew Scott <3648487+ayyess@users.noreply.github.com> Date: Thu, 27 Jul 2023 12:37:50 +0100 Subject: [PATCH] Support commands with multiple read-chars This change fixes commands that read-chars multiple times. Previously, two stage commands like embrace-change would read the same char twice immediately and avy-goto-char-timer would never stop reading input as a cached value was always provided during the timer. Instead, the read-char prompt is included in the cache key so that multiple different calls are cached separately and accessible by the fake cursors. --- features/multiple-cursors-core.feature | 7 ++++ .../multiple-cursors-steps.el | 5 +++ multiple-cursors-core.el | 42 ++++++++----------- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/features/multiple-cursors-core.feature b/features/multiple-cursors-core.feature index dd97811..0c4ed5c 100644 --- a/features/multiple-cursors-core.feature +++ b/features/multiple-cursors-core.feature @@ -50,6 +50,13 @@ Feature: Multiple cursors core And I press "C-!" Then I should see "This aatext contains the word aatext twice" +Scenario: Unknown command with multiple read: yes, do for all + Given I have bound C-! to a new command that inserts two read-chars + And I have cursors at "text" in "This text contains the word text twice" + When I press "C-! b c y" + And I press "C-! d e" + Then I should see "This bcdetext contains the word bcdetext twice" + Scenario: Unknown command: no, don't do for all Given I have bound C-! to another new command that inserts "a" And I have cursors at "text" in "This text contains the word text twice" diff --git a/features/step-definitions/multiple-cursors-steps.el b/features/step-definitions/multiple-cursors-steps.el index 2eb442e..0c299a9 100644 --- a/features/step-definitions/multiple-cursors-steps.el +++ b/features/step-definitions/multiple-cursors-steps.el @@ -130,6 +130,11 @@ (defun mc-test-temp-command-2 () (interactive) (insert ins)) (global-set-key (kbd "C-!") 'mc-test-temp-command-2)))) +(Given "^I have bound C-! to a new command that inserts two read-chars$" + (lambda () + (defun mc-test-temp-command-3 () (interactive) (insert (read-char "first: ")) (insert (read-char "second: "))) + (global-set-key (kbd "C-!") 'mc-test-temp-command-3))) + (Given "^I have bound C-! to a keyboard macro that insert \"_\"$" (lambda () (fset 'mc-test-temp-kmacro "\C-q_") diff --git a/multiple-cursors-core.el b/multiple-cursors-core.el index 3d48684..02a9e75 100644 --- a/multiple-cursors-core.el +++ b/multiple-cursors-core.el @@ -28,8 +28,6 @@ (require 'cl-lib) (require 'rect) -(defvar mc--read-char) - (defface mc/cursor-face '((t (:inverse-video t))) "The face used for fake cursors" @@ -52,11 +50,6 @@ rendered or shift text." :type '(boolean) :group 'multiple-cursors) -(defcustom mc--reset-read-variables '() - "A list of cache variable names to reset by multiple-cursors." - :type '(list symbol) - :group 'multiple-cursors) - (defface mc/region-face '((t :inherit region)) "The face used for fake regions" @@ -326,34 +319,35 @@ cursor with updated info." ;; Intercept some reading commands so you won't have to ;; answer them for every single cursor -(defvar multiple-cursors-mode nil) +(defvar mc--input-function-cache nil) (defun mc--reset-read-prompts () - (mapc (lambda (var) (set var nil)) - mc--reset-read-variables)) + (setq mc--input-function-cache nil)) -(defmacro mc--cache-input-function (fn-name) +(defmacro mc--cache-input-function (fn-name args-cache-key-fn) "Advise FN-NAME to cache its value in a private variable. Cache is to be used by mc/execute-command-for-all-fake-cursors and -caches will be reset by mc--reset-read-prompts." +caches will be reset by mc--reset-read-prompts. ARGS-CACHE-KEY-FN +should transform FN-NAME's args to a unique cache-key so that +different calls to FN-NAME during a command can return multiple +values." (let ((mc-name (intern (concat "mc--" (symbol-name fn-name))))) `(progn - (defvar ,mc-name nil) (defun ,mc-name (orig-fun &rest args) (if (not multiple-cursors-mode) (apply orig-fun args) - (unless ,mc-name - (setq ,mc-name (apply orig-fun args))) - ,mc-name)) - (advice-add ',fn-name :around #',mc-name) - (add-to-list 'mc--reset-read-variables ',mc-name)))) + (let* ((cache-key (cons ,(symbol-name fn-name) (,args-cache-key-fn args))) + (cached-value (assoc cache-key mc--input-function-cache)) + (return-value (if cached-value (cdr cached-value) (apply orig-fun args)))) + (unless cached-value + (push (cons cache-key return-value) mc--input-function-cache)) + return-value))) + (advice-add ',fn-name :around #',mc-name)))) -(mc--cache-input-function read-char) -(mc--cache-input-function read-quoted-char) -(mc--cache-input-function register-read-with-preview) ; used by insert-register -(mc--cache-input-function read-char-from-minibuffer) ; used by zap-to-char - -(mc--reset-read-prompts) +(mc--cache-input-function read-char car) +(mc--cache-input-function read-quoted-char car) +(mc--cache-input-function register-read-with-preview car) ; used by insert-register +(mc--cache-input-function read-char-from-minibuffer car) ; used by zap-to-char (defun mc/fake-cursor-p (o) "Predicate to check if an overlay is a fake cursor"