evil-mc icon indicating copy to clipboard operation
evil-mc copied to clipboard

Can we have a evil-mc-edit-lines just like mc/edit-lines?

Open 3rd3 opened this issue 8 years ago • 34 comments

I found this feature very handy in the multiple-cursors package, and it would great if it would also work with visual block to insert the cursors at a particular column. If you provide me some ideas how to do it and if it is not so hard to do, I can maybe write a patch myself.

3rd3 avatar Feb 26 '16 22:02 3rd3

Creating a cursor on multiple lines could be accomplished now if you can find some identifiable character on each line such as an equal sign or an ending semicolon. Then you could just press v on that character on the first line and then C-n to create cursors on the other lines. If that's not the case then you could press grs to stop cursors, then grh on every line you need a cursor, and finally grr to resume the cursors.

gabesoft avatar Feb 27 '16 07:02 gabesoft

If none of the options I mentioned above work, then you could write a function that finds all the selected lines, moves the cursor to each selected line, and calls evil-mc-make-cursor-here. You'd also have to call evil-mc-pause-cursors at the beginning and evil-mc-resume-cursors at the end. When I'll have time I'll implement this function myself but I may not be available to work on this for a while.

gabesoft avatar Feb 27 '16 07:02 gabesoft

I wrote a function along these lines. With a bit of polish, I could open a PR. There could be a keybinding to it from evil-visual map, that way people could use it in much the same circumstances that they currently use block visual mode insert. I use it for this. The benefit is that unlike with block visual insert, you can also move the cursors to different locations and execute on text objects, etc. It's a bit more like writing a macro that you execute on every line, but way nicer because it's fully interactive.

Would you be willing to merge such a PR?

quicknir avatar Jul 19 '16 14:07 quicknir

If it doesn't introduce any complexity to the rest of the code, and if you're willing to add some tests for it I would be willing to merge a PR for this.

gabesoft avatar Jul 19 '16 15:07 gabesoft

Where are the current tests in the code, and how are they structured? Searching "test" on github in the repo didn't bring up any hits in the actual code.

quicknir avatar Jul 19 '16 15:07 quicknir

They are in the features folder. To run the tests do a make test in the evil-mc folder.

gabesoft avatar Jul 19 '16 15:07 gabesoft

Actually, I'm thinking of making another plugin called evil-mc-extras that will contain functions that build on the core evil-mc functionality. I have a couple of such functions myself. You can get your PR ready anyway, but just a heads up that you may have to open it on the new repo. Although, if I'm taking too long, I may just merge your PR and then move it to the new plugin myself.

gabesoft avatar Jul 19 '16 15:07 gabesoft

That's fine, if you open up evil-mc-extras, can you submit a PR to spacemacs to have it added as part of evil-mc? By the way I had never seen tests for elisp code before, I don't know if that's how its always done, but that's the coolest thing, and so much easier than I expected.

quicknir avatar Jul 19 '16 15:07 quicknir

Yea, they are pretty cool. You may want too take look at this https://github.com/ecukes/espuds for the steps definitions. More steps are in features/step-definitions/evil-mc-steps.el

gabesoft avatar Jul 19 '16 15:07 gabesoft

I've added https://github.com/gabesoft/evil-mc-extras. It's still pending approval from melpa and the tests are failing pending a change I made in evil-mc. But, that's where you should add your function and tests. Should be easier. Before you run the tests don't forget to run make test-install. Also, add some info in the README

gabesoft avatar Jul 19 '16 18:07 gabesoft

Ok the extras plugin has been merged into melpa and the tests pass. So, feel free to submit your PR there whenever you're ready. Then we can add it to spacemacs as well. You don't have to wait for my PR for that, just create a layer in your spacemacs fork, similar to https://github.com/gabesoft/spacemacs/commit/3306460faacbc1491cffe28e2b61f25e71a4f18b (see .spacemacs.custom.layers.el and layers/evil-mc-extras/packages.el in that commit)

gabesoft avatar Jul 22 '16 07:07 gabesoft

Hi, I am looking for the line-edit-plugin in evil-mc-extras repo, but I didn't find it. Is @quicknir still going adding this function?

yea107 avatar Aug 31 '16 00:08 yea107

Not sure. He didn't submit a pull request yet.

gabesoft avatar Aug 31 '16 00:08 gabesoft

@yea107 @gabesoft

Hey guys, very sorry. I've actually been moving apartments with my 9 month pregnant wife and just didn't really have time to breath in August. I can't drive this forward right now as I expect I'll have even less free time shortly. What I can do though is post the code I have, it's fairly simple. yeah107, I'm not sure what your elisp proficiency level is but I'm pretty sure you can pick this up, fix the minor issues with it, and with some guidance from gabesoft on the tests, get this merged. I'm happy to answer questions or address quick comments. The code I have:

  (defun column-number-at-pos (pos)
    (save-excursion (goto-char pos) (current-column))
  )
  (defun make-vertical-cursors (p1 p2)
    "makes vertical cursors"
    (interactive "r")
    (evil-mc-pause-cursors)
    (let ((start-row (line-number-at-pos p1))
          (end-row (line-number-at-pos p2))
          (start-col (min (column-number-at-pos p1) (column-number-at-pos p2)))
          )
      (save-excursion
        (goto-char p1)
        (move-to-column start-col)
        (while (< start-row end-row)
          (call-interactively 'evil-mc-make-cursor-here)
          (forward-line 1)
          (move-to-column start-col)
          (setq start-row (+ start-row 1))
          )
        )
      (call-interactively 'evil-exit-visual-state)
      (call-interactively 'evil-mc-resume-cursors)
      (goto-char p2)
      (move-to-column start-col)
      )
    )

This function is designed to be called from block visual mode. It basically does a bunch of arithmetic to get the leftmost column, and the row numbers from beginning to end. Then it creates a cursor at that column, on every row from top to one before last, and it leaves the actual cursor on the last row leftmost column. It works fine for me in general, there's three things that would need to be investigated:

  1. Whether its handling of various edge cases is satisfactory; when you highlight all the way to the last line, etc.
  2. Handling of empty lines, or even just lines where the last text does not extend as far as the leftmost column of the highlighted region. Currently, it makes a cursor on these lines as well, and it simply ends up getting placed at the rightmost column available. This is probably not ideal. Personally I think it should be consistent with block visual insert mode; my interpretation of that in this context is that cursors simply should not be created on those lines (though I'm open to arguments for/against).
  3. It's probably a bit odd that if you enter block visual mode, then go up a few rows and run the function, you discover that the real cursor is at the bottom. In that situation it should probably create the real cursor at the top.

3 is pretty trivial to resolve, 1 and 2 aren't so bad. And of course tests have to be written which doesn't seem so bad either. So, are you ready for the torch @yea107 ? :-)

quicknir avatar Sep 01 '16 14:09 quicknir

Note wrt to 2: the alternative to aiming for consistency with block visual insert, would be consistency with block visual append. If you visual block select a vertical line, and some of the lines in the middle don't go out to the selection, then insert will not write anything on those lines, and append will write exactly aligned text on those lines as well. So in that sense, the two options would seem to be: don't create cursors on those lines, or create a bunch of whitespace on those lines to get aligned, and then create the cursors. I think that the first should be supported for sure as there are just too many cases where empty lines separate useful stuff that you want to block edit, but possibly the second version could be supported as well with an option. You could have: insert-vertical-cursors, and append-vertical-cursors, which I think would be pretty cool.

quicknir avatar Sep 01 '16 15:09 quicknir

Thank you for the kindly tips @quicknir, I will do my best to finish this work. Actually it's my first time to try the test with elisp, but I think it must be cool.

BTW, I have a one month pregnant wife, too. Wish you have a good time with your family.

yea107 avatar Sep 01 '16 16:09 yea107

@yea107 Thanks, appreciate that, best of luck to you as well!

quicknir avatar Sep 01 '16 18:09 quicknir

@yea107, were you able to use the function above to create a cursor in each visually selected line?

ninrod avatar Sep 23 '16 22:09 ninrod

Hey all, any progress on this, or should I start working on this again? @yea107 ?

quicknir avatar Oct 29 '16 01:10 quicknir

I found I am not familiar enough with developing in emacs, so it takes time more than I imaged. I think you will spend much less time than me, so it's is the better way you start working on this again. Sorry for not being helpful. @quicknir

yea107 avatar Oct 29 '16 02:10 yea107

Just use rectangle.el. For example, something like this:

(defun evil--mc-make-cursor-at-col (startcol _endcol orig-line)
  (move-to-column startcol)
  (unless (= (line-number-at-pos) orig-line)
    (evil-mc-make-cursor-here)))

(defun evil-mc-make-vertical-cursors (beg end)
  (interactive (list (region-beginning) (region-end)))
  (evil-mc-pause-cursors)
  (apply-on-rectangle #'evil--mc-make-cursor-at-col
                      beg end (line-number-at-pos (point)))
  (evil-mc-resume-cursors)
  (evil-normal-state)
  (move-to-column (evil-mc-column-number (if (> end beg)
                                             beg
                                           end))))

Feel free to work with this code. It handles 3 (the point will remain on the same line). 2 would be easy to implement. I don't want to work on this right now, but if this doesn't go anywhere in the next few weeks, I can write tests and make a pull request (feel free to ping me here to ask me to do it then).

It seems this could be pretty useful when used with evil-textobj-column .

Also, to me, this seems like it should be a core functionality of evil-mc.

noctuid avatar Dec 16 '16 19:12 noctuid

@noctuid Haha man your code made mine look like something the cat dragged in. Okay so I took this code and walked with it (ran is too strong a word).

  (defun evil--mc-make-cursor-at-col-append (_startcol endcol orig-line)
    (end-of-line)
    (when (> endcol (current-column))
      (insert-char ?\s (- endcol (current-column))))
    (move-to-column (- endcol 1))
    (unless (= (line-number-at-pos) orig-line)
      (message (current-column))
      (evil-mc-make-cursor-here)))

  (defun evil--mc-make-cursor-at-col-insert (startcol _endcol orig-line)
    (end-of-line)
    (unless (or (= (line-number-at-pos) orig-line) (> startcol (last-column)))
      (move-to-column startcol)
      (evil-mc-make-cursor-here)))

  (defun evil--mc-make-vertical-cursors (beg end func)
    (evil-mc-pause-cursors)
    (apply-on-rectangle func
                        beg end (line-number-at-pos (point)))
    (evil-mc-resume-cursors)
    (evil-normal-state)
    (move-to-column (evil-mc-column-number (min beg end)))
    )

  (defun evil-mc-insert-vertical-cursors (beg end)
    (interactive (list (region-beginning) (region-end)))
    (evil--mc-make-vertical-cursors beg end 'evil--mc-make-cursor-at-col-insert))

  (defun evil-mc-append-vertical-cursors (beg end)
    (interactive (list (region-beginning) (region-end)))
    (evil--mc-make-vertical-cursors beg end 'evil--mc-make-cursor-at-col-append))

  (evil-define-key 'visual global-map "gI" 'evil-mc-insert-vertical-cursors)
  (evil-define-key 'visual global-map "gA" 'evil-mc-append-vertical-cursors)

In the process, I discovered what I think is a bug in evil-mc. Try the following:

  1. Go to an empty line.
  2. Enter insert mode, hit space a few times, and go back to normal mode.
  3. Create a multiple cursor here.
  4. Now try to go back into insert mode, e.g. with i. The spaces that have been inserted vanish, and the cursor jumps to the beginning of the line.

Anybody else notice this? This is causing my append function to fail in certain cases, when one of the lines in the middle is completely empty. Otherwise both functions seem to work.

quicknir avatar Dec 18 '16 03:12 quicknir

That does seem like a bug in evil-mc. I was able to reproduce it.

gabesoft avatar Dec 18 '16 04:12 gabesoft

I fixed the empty line bug in this commit 2a550281d6081c94304b3206b43d94cfc2fc5db2. However, the fix only addresses entering insert mode via i. It still occurs when entering insert mode via a or A or I

gabesoft avatar Dec 20 '16 02:12 gabesoft

@quicknir, I think that function is not working well. gI gives me >: Symbol’s function definition is void: last-column.

gA gives me evil--mc-make-cursor-at-col-append: Wrong type argument: wholenump, -1

ninrod avatar Jan 18 '17 00:01 ninrod

for the record, @quicknir, this function you've wrote here:

  (defun column-number-at-pos (pos)
    (save-excursion (goto-char pos) (current-column)))
  (defun make-vertical-cursors (p1 p2)
    "makes vertical cursors"
    (interactive "r")
    (evil-mc-pause-cursors)
    (let ((start-row (line-number-at-pos p1))
          (end-row (line-number-at-pos p2))
          (start-col (min (column-number-at-pos p1) (column-number-at-pos p2))))
      (save-excursion
        (goto-char p1)
        (move-to-column start-col)
        (while (< start-row end-row)
          (call-interactively 'evil-mc-make-cursor-here)
          (forward-line 1)
          (move-to-column start-col)
          (setq start-row (+ start-row 1))))
      (call-interactively 'evil-exit-visual-state)
      (call-interactively 'evil-mc-resume-cursors)
      (goto-char p2)
      (move-to-column start-col)))

is actually working. I'd like that to work with vip though.

suppose I have a decently sized paragraph of code. I press vip and select that. Then I press, for example, C-n and because you know I've selected those lines, you can create multiple cursors on the beggining of those lines.

That would be awesome.

ninrod avatar Jan 18 '17 00:01 ninrod

Hum, actually, @noctuid's function works exactly like I just described. my journey is complete. I've completely migrated my vim configuration. @noctuid you are my hero.

  (defun evil--mc-make-cursor-at-col (startcol _endcol orig-line)
    (move-to-column startcol)
    (unless (= (line-number-at-pos) orig-line)
      (evil-mc-make-cursor-here)))

  (defun evil-mc-make-vertical-cursors (beg end)
    (interactive (list (region-beginning) (region-end)))
    (evil-mc-pause-cursors)
    (apply-on-rectangle #'evil--mc-make-cursor-at-col
                        beg end (line-number-at-pos (point)))
    (evil-mc-resume-cursors)
    (evil-normal-state)
    (move-to-column (evil-mc-column-number (if (> end beg)
                                               beg
                                             end))))

ninrod avatar Jan 18 '17 01:01 ninrod

Here's the full working scope for completeness:

  (use-package evil-mc
    :ensure t
    :config
    (global-evil-mc-mode  1)

    (defun evil--mc-make-cursor-at-col (startcol _endcol orig-line)
      (move-to-column startcol)
      (unless (= (line-number-at-pos) orig-line)
        (evil-mc-make-cursor-here)))
    (defun evil-mc-make-vertical-cursors (beg end)
      (interactive (list (region-beginning) (region-end)))
      (evil-mc-pause-cursors)
      (apply-on-rectangle #'evil--mc-make-cursor-at-col
                          beg end (line-number-at-pos (point)))
      (evil-mc-resume-cursors)
      (evil-normal-state)
      (move-to-column (evil-mc-column-number (if (> end beg)
                                                 beg
                                               end)))))

ninrod avatar Jan 18 '17 01:01 ninrod

@ninrod Well, that code does not handle quite a few things, in particular the handling of "short" lines in insert (do not make cursor) vs append (add whitespace) modes. As for vip, you cannot do vip in vim and then block insert; you'd need to first switch to block visual with C-v. So it's not really duplicating a vim workflow per se. The code below correctly handles insert and append. It creates an extra cursor if you try to do vip and then gI, but I'm not really sure whether this case should be handled specially or not; it's only really meant to be called from rectangular visual mode (block visual).

  (global-evil-mc-mode 1)

  (defun col-at-point (point)
    (save-excursion (goto-char point) (current-column)))

  (defun evil--mc-make-cursor-at-col-append (_startcol endcol orig-line)
    (end-of-line)
    (when (> endcol (current-column))
      (insert-char ?\s (- endcol (current-column))))
    (move-to-column (- endcol 1))
    (unless (= (line-number-at-pos) orig-line)
      (evil-mc-make-cursor-here)))

  (defun evil--mc-make-cursor-at-col-insert (startcol _endcol orig-line)
    (end-of-line)
    (move-to-column startcol)
    (unless (or (= (line-number-at-pos) orig-line) (> startcol (current-column)))
      (evil-mc-make-cursor-here)))

  (defun evil--mc-make-vertical-cursors (beg end func)
    (evil-mc-pause-cursors)
    (apply-on-rectangle func
                        beg end (line-number-at-pos (point)))
    (evil-mc-resume-cursors)
    (evil-normal-state))

  (defun evil-mc-insert-vertical-cursors (beg end)
    (interactive (list (region-beginning) (region-end)))
    (evil--mc-make-vertical-cursors beg end 'evil--mc-make-cursor-at-col-insert)
    (move-to-column (min (col-at-point beg) (col-at-point end))))

  (defun evil-mc-append-vertical-cursors (beg end)
    (interactive (list (region-beginning) (region-end)))
    (evil--mc-make-vertical-cursors beg end 'evil--mc-make-cursor-at-col-append)
    (move-to-column (- (max (col-at-point beg) (col-at-point end)) 1)))

  (evil-define-key 'visual global-map "gI" 'evil-mc-insert-vertical-cursors)
  (evil-define-key 'visual global-map "gA" 'evil-mc-append-vertical-cursors)

quicknir avatar Jan 18 '17 01:01 quicknir

@quicknir, this last function of your's is working nicely. Thanks!

but sometimes, it creates an extra cursor after the last line that I've just selected. Are you seeing this also?

ninrod avatar Jan 18 '17 01:01 ninrod