gabesoft / evil-mc

Multiple cursors implementation for evil-mode
MIT License
383 stars 35 forks source link

Stored registers are removed on multi cursor creation #83

Open duianto opened 5 years ago

duianto commented 5 years ago

Observed behavior

Stored registers are removed when multiple cursors are created.

Expected behavior

That stored registers remained after cursor creation.

Steps to reproduce

Notes

Any registers that were stored while multiple cursors were active. Remain after pausing, resuming or undoing the cursors.

System information

Evil-mc Version: 0.0.4 (20180921.1727)
Evil version evil-git-9865e7731
GNU Emacs 26.1 (build 1, x86_64-w64-mingw32) of 2018-05-30
Windows 10 Version 1803
dchrzanowski commented 5 years ago

Same spotted on Linux Emacs 26.1.

dchrzanowski commented 5 years ago

If someone has a good elisp fu, then perhaps an easy solution would be to make a copy of all text registers before the multiple cursors are made. Then when the cursors are killed the registers are restored...food for thought.

EDIT: For the time being I am removing register-alist here and then byte-recompiling the file. Works just fine, it's not that I need named registers while multiple cursors are active anyway.

duianto commented 5 years ago

Update

No, the "fix" below only works when creating additional cursors with (evil-mc-make-cursor-here). The named registers are still removed if one creates a cursor with for example: (evil-mc-make-and-goto-next-match).

A to limited solution

I might have found a possible solution that doesn't remove the named registers. By wrapping the call to (evil-mc-cursors-before) in a temporary register-alist variable.

(defun evil-mc-run-cursors-before ()
  "Runs `evil-mc-cursors-before' if there are no cursors created yet."
  (when (not (evil-mc-has-cursors-p))
    (let (register-alist)
      (evil-mc-cursors-before))))

The optimal solution would be to be able to paste from a register at every cursor, but with this "fix" it only pastes from a register at the main cursor.

But the major issue of not removing the named registers is probably enough for now.

sooqua commented 4 years ago

If someone has a good elisp fu, then perhaps an easy solution would be to make a copy of all text registers before the multiple cursors are made. Then when the cursors are killed the registers are restored...food for thought.

EDIT: For the time being I am removing register-alist here and then byte-recompiling the file. Works just fine, it's not that I need named registers while multiple cursors are active anyway.

(setq ~+multiple-cursors--evil-mc-registers-copy '())

(defun ~+multiple-cursors*evil-mc-run-cursors-before (&rest r)
  (cl-loop for reg from 97 to 122 do
           (setq ~+multiple-cursors--evil-mc-registers-copy (nconc ~+multiple-cursors--evil-mc-registers-copy (cons (get-register reg) nil)))))

(defun ~+multiple-cursors*evil-mc-run-cursors-after (&rest r)
  (cl-loop for reg in ~+multiple-cursors--evil-mc-registers-copy
           for i from 97 do
           (when reg
             (set-register i reg)))
  (setq ~+multiple-cursors--evil-mc-registers-copy nil))

(advice-add #'evil-mc-run-cursors-before :before #'~+multiple-cursors*evil-mc-run-cursors-before)
(advice-add #'evil-mc-run-cursors-after :after #'~+multiple-cursors*evil-mc-run-cursors-after)
duianto commented 4 years ago

@sooqua That only seems to work with: evil-mc-make-and-goto-next-match and evil-mc-make-and-goto-prev-match

It doesn't work with: evil-mc-make-cursor-here or evil-mc-make-all-cursors

And there might be an off by one issue, because while I was storing text in the a, b and c registers and creating evil-mc cursors, then it would sometimes show that the same text was stored in the } and | registers.

duianto commented 4 years ago

The author of this Spacemacs issue: Problem persisting registers https://github.com/syl20bnr/spacemacs/issues/12606

Says:

On exiting spacemacs the register list seems to persist alphabetic and numeric registers differently, as regards the buffer name.

In the - rather longer than is probably necessary - line below from .emacs.desktop the numeric registers seems to be stored along with the full path name of the buffer. Whereas a lot of registers seem to refer only to the file name.

That might be why the alphabetic registers are erased when evil-mc cursors are created.

sooqua commented 4 years ago

Quick fix:

(defun ~+multiple-cursors*evil-mc-write-cursor-state (state)
  "Write the state of the real cursor with values from STATE."
  (let ((names (evil-mc-get-cursor-variables)))
    (dolist (name names)
      (when (boundp name)
        (let ((p (evil-mc-get-cursor-property state name)))
          (when (not (and (string= name "register-alist") (null p)))
            (set name (evil-mc-get-cursor-property state name))))))))
(advice-add #'evil-mc-write-cursor-state :override #'~+multiple-cursors*evil-mc-write-cursor-state)
duianto commented 4 years ago

That seems to work for all evil-mc cursor creation commands 👍

It seems to also keep the 0 register from being deleted. There's an open issue about it: Nils out evil-was-yanked-without-register #70

duianto commented 4 years ago

Could that solution or something similar be implemented to also prevent the position markers from being removed when evil-mc cursors are created?

Position markers (I might not be the correct name), can be store with ma, mb, etc. And jumped to with:

sooqua commented 4 years ago

Yeah. I'm not sure why (null p) check doesn't work with evil-markers-alist, it seems like it doesn't write null in there but something breaks anyway.

(defun ~+multiple-cursors-evil-mc-write-cursor-state-a (state)
  "Write the state of the real cursor with values from STATE."
  (let ((names (evil-mc-get-cursor-variables)))
    (dolist (name names)
      (when (boundp name)
        (let ((p (evil-mc-get-cursor-property state name)))
          (when (not
                 (or
                  (eq name 'register-alist)
                  (eq name 'evil-markers-alist)))
            (set name p)))))))
(advice-add #'evil-mc-write-cursor-state :override #'~+multiple-cursors-evil-mc-write-cursor-state-a)
sooqua commented 4 years ago

You could probably just remove register-alist and evil-markers-alist from evil-mc-cursor-variables at this point instead of overriding evil-mc-write-cursor-state, for some negligible performance benefit, but I don't know what these variables are used for anyway.

(when (featurep! :editor multiple-cursors)
  (after! evil-mc
    (setq evil-mc-cursor-variables (mapcar
                                    (lambda (s)
                                      (remove 'register-alist (remove 'evil-markers-alist s)))
                                    evil-mc-cursor-variables))))

I'm using Doom Emacs, you will have to modify everything before and including after! to run this code only after evil-mc is loaded.

duianto commented 4 years ago

This seems to work to prevent the removal of:

(with-eval-after-load 'evil-mc
  (setq evil-mc-cursor-variables
        (mapcar
          (lambda (s)
            (remove 'register-alist
                    (remove 'evil-markers-alist
                            (remove evil-was-yanked-without-register s))))
          evil-mc-cursor-variables)))

Thanks