Open 3rd3 opened 8 years ago
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.
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.
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?
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.
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.
They are in the features
folder. To run the tests do a make test
in the evil-mc
folder.
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.
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.
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
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
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)
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?
Not sure. He didn't submit a pull request yet.
@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:
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 ? :-)
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.
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 Thanks, appreciate that, best of luck to you as well!
@yea107, were you able to use the function above to create a cursor in each visually selected line?
Hey all, any progress on this, or should I start working on this again? @yea107 ?
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
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 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:
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.
That does seem like a bug in evil-mc
. I was able to reproduce it.
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
@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
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.
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))))
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 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, 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 It will only create an extra cursor if you have selected the newline character at the end of the last line. For instance, when you do vip
it enters visual line mode automatically, in visual line mode the whole line is selected including the newline character. This turns out to be an edge case in the code above and it will then create one extra vertical cursor.
Whether this should be checked with a conditional and adjusted is a reasonable question. It may be convenient for your use case, though I will point out that this is mimicking vim's block insert/append, which can only be initiated from block visual mode, not regular visual or line visual.
@quicknir 's code above worked for me for over a year, but a few months ago it failed where only the last cursor does anything. There's also this code snippet here , but it has the same problem.
evil-mc-recording-debug-on sends this:
evil-mc Failed to execute org-self-insert-command with error: Args out of range: [], -1 [3 times]
Any ideas what happened, or what to do?
okay, I tried debug function with a non-orgmode buffer and received a more verbose return
evil-mc Command evil-insert i
evil-mc Record-begin ((:undo-list-pointer-pre nil (nil rear-nonsticky nil 482 . 483) (
. -832) (482 . 833) 355 nil (nil rear-nonsticky nil 354 . 355) (
. -482) (354 . 483) 353 nil (nil rear-nonsticky nil 353 . 354) (nil fontified nil 353 . 354) (353 . 354) nil undo-tree-canary) (:evil-state-begin . normal) (:keys-pre-with-count nil evil-insert [105] nil) (:keys-pre . [105]) (:name . evil-insert))
evil-mc Record-motion pre i [105] normal
evil-mc Record-motion post i [105] insert
evil-mc Record-finish ((:undo-list-pointer-post nil (nil rear-nonsticky nil 482 . 483) (
. -832) (482 . 833) 355 nil (nil rear-nonsticky nil 354 . 355) (
. -482) (354 . 483) 353 nil (nil rear-nonsticky nil 353 . 354) (nil fontified nil 353 . 354) (353 . 354) nil undo-tree-canary) (:keys-post-raw . [105]) (:keys-post . []) (:last-input . 105) (:evil-state-end . insert) (:keys-motion-post . [105]) (:keys-motion-pre . [105]) (:undo-list-pointer-pre nil (nil rear-nonsticky nil 482 . 483) (
. -832) (482 . 833) 355 nil (nil rear-nonsticky nil 354 . 355) (
. -482) (354 . 483) 353 nil (nil rear-nonsticky nil 353 . 354) (nil fontified nil 353 . 354) (353 . 354) nil undo-tree-canary) (:evil-state-begin . normal) (:keys-pre-with-count nil evil-insert [105] nil) (:keys-pre . [105]) (:name . evil-insert)) evil-insert
evil-mc Record-done evil-insert pre i post keys-motion i keys-operator count nil keys i
evil-mc Command mwheel-scroll [(wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1)]
evil-mc Record-begin ((:undo-list-pointer-pre nil (nil rear-nonsticky nil 482 . 483) (
. -832) (482 . 833) 355 nil (nil rear-nonsticky nil 354 . 355) (
. -482) (354 . 483) 353 nil (nil rear-nonsticky nil 353 . 354) (nil fontified nil 353 . 354) (353 . 354) nil undo-tree-canary) (:evil-state-begin . insert) (:keys-pre-with-count nil mwheel-scroll [(wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1)] nil) (:keys-pre . [(wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1)]) (:name . mwheel-scroll))
evil-mc Record-motion pre [(wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1)] [(wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1)] insert
evil-mc Record-motion post [(wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1)] [(wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1)] insert
evil-mc Record-finish ((:undo-list-pointer-post nil (nil rear-nonsticky nil 482 . 483) (
. -832) (482 . 833) 355 nil (nil rear-nonsticky nil 354 . 355) (
. -482) (354 . 483) 353 nil (nil rear-nonsticky nil 353 . 354) (nil fontified nil 353 . 354) (353 . 354) nil undo-tree-canary) (:keys-post-raw . [(wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1) (wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1)]) (:keys-post . []) (:last-input wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1) (:evil-state-end . insert) (:keys-motion-post . [(wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1)]) (:keys-motion-pre . [(wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1)]) (:undo-list-pointer-pre nil (nil rear-nonsticky nil 482 . 483) (
. -832) (482 . 833) 355 nil (nil rear-nonsticky nil 354 . 355) (
. -482) (354 . 483) 353 nil (nil rear-nonsticky nil 353 . 354) (nil fontified nil 353 . 354) (353 . 354) nil undo-tree-canary) (:evil-state-begin . insert) (:keys-pre-with-count nil mwheel-scroll [(wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1)] nil) (:keys-pre . [(wheel-down (#<window 3867 on *Messages*> 426937 (282 . 375) 518397004 nil 426937 (40 . 28) nil (2 . 11) (7 . 13)) 1 1)]) (:name . mwheel-scroll)) mwheel-scroll
evil-mc Record-done mwheel-scroll pre post keys-motion keys-operator count nil keys
evil-mc Failed to execute mwheel-scroll with error: Bad binding in mwheel-scroll [3 times]
evil-mc Command mwheel-scroll [(double-wheel-down (#<window 3867 on *Messages*> 427008 (282 . 375) 518397108 nil 427008 (40 . 28) nil (198 . 11) (7 . 13)) 2 3)]
Error in pre-command-hook (evil-mc-begin-command-save): (user-error "No command bound to [(double-wheel-down (#<window 3867 on *Messages*> 427008 (282 . 375) 518397108 nil 427008 (40 . 28) nil (198 . 11) (7 . 13)) 2 3)]")
evil-mc Record-motion pre [(double-wheel-down (#<window 3867 on *Messages*> 427008 (282 . 375) 518397108 nil 427008 (40 . 28) nil (198 . 11) (7 . 13)) 2 3)] [(double-wheel-down (#<window 3867 on *Messages*> 427008 (282 . 375) 518397108 nil 427008 (40 . 28) nil (198 . 11) (7 . 13)) 2 3)] insert
evil-mc Record-motion post [(double-wheel-down (#<window 3867 on *Messages*> 427008 (282 . 375) 518397108 nil 427008 (40 . 28) nil (198 . 11) (7 . 13)) 2 3)] [(double-wheel-down (#<window 3867 on *Messages*> 427008 (282 . 375) 518397108 nil 427008 (40 . 28) nil (198 . 11) (7 . 13)) 2 3)] insert
evil-mc Record-finish ((:undo-list-pointer-post nil (nil rear-nonsticky nil 482 . 483) (
. -832) (482 . 833) 355 nil (nil rear-nonsticky nil 354 . 355) (
. -482) (354 . 483) 353 nil (nil rear-nonsticky nil 353 . 354) (nil fontified nil 353 . 354) (353 . 354) nil undo-tree-canary) (:keys-post-raw . [(double-wheel-down (#<window 3867 on *Messages*> 427008 (282 . 375) 518397108 nil 427008 (40 . 28) nil (198 . 11) (7 . 13)) 2 3) (double-wheel-down (#<window 3867 on *Messages*> 427008 (282 . 375) 518397108 nil 427008 (40 . 28) nil (198 . 11) (7 . 13)) 2 3)]) (:keys-post . []) (:last-input double-wheel-down (#<window 3867 on *Messages*> 427008 (282 . 375) 518397108 nil 427008 (40 . 28) nil (198 . 11) (7 . 13)) 2 3) (:evil-state-end . insert) (:keys-motion-post . [(double-wheel-down (#<window 3867 on *Messages*> 427008 (282 . 375) 518397108 nil 427008 (40 . 28) nil (198 . 11) (7 . 13)) 2 3)]) (:keys-motion-pre . [(double-wheel-down (#<window 3867 on *Messages*> 427008 (282 . 375) 518397108 nil 427008 (40 . 28) nil (198 . 11) (7 . 13)) 2 3)])) mwheel-scroll
evil-mc Record-done nil pre post keys-motion keys-operator count nil keys
EDIT: Nevermind, I saw the snip in the README for multi-line cursors. Ignore this.
Update II: now both @quicknir 's code, and the other snippet, just worked in the same buffer (scratch buffer)
But, both still fail in an orgmode buffer, with same error:
evil-mc Failed to execute org-self-insert-command with error: Args out of range: [], -1 [52 times]
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.