And I need to edit around a limited version of foo way more often than I need to edit all instances.
:.,.+5s/findtext/replacetext/g
In neovim, you'll see a preview of the changes as you type the regexp. Neovim is really good.I could type ":.,.+5s/findtext/replacetext/g" in vim (and remember the syntax!) or i just could do "ctrl-d, ctrl-d, ctrl-d, ctrl-d, ctrl-d, replacetext" and have a visual, immediate confirmation on what i am doing. But i bet you, that i'll press ctrl-d 5 times faster than you entering ":.,.+5s/".
Remembering the syntax is not a problem here. Learning it in the first place, on the other hand...
I do like multiple cursors though. I end up switching to VSCode a couple times a week to do things where it's the right tool for the job.
Vim users don't have to choose between precision and ease of use as you can build macros or mappings that match ANY workflow (i.e. powerful, customized workflows that minimize the need to remember exact syntax or to type it all out), for example achieving results similar to multiple cursors but with Vim's inherent efficiency and scalability. So, while Sublime Text's multiple cursors are handy, Vim can match - and even exceed - this functionality with very little setup (one time setup!).
"foo bar" # For the 1st line
"baz qux" # For the 2nd line
into "bar" # For the foo key
"qux" # For the baz key
With multi cursors, you could select foo and baz and then interactively edit the rest of the line. I'm capable of doing that with vim, but by the time I'd even settled on an approach for it, I'd already be done in the other editor.I could use Emacs macros to do the same kind of thing, and I've probably used `C-x (` a zillion times to build procedures I could then run 30 more times on the following lines. For quick interaction, I still prefer multi cursors.
This is two lines, if everything was a different length, I think by the time I got multiple cursors to do this, I would have long managed by just doing the same editing task twice (cut overwrite-paste then cut overwrite-paste).
If I had to do this for many more lines I would have just used a substitution:
:s/\(.*\)"\(\w\+\) \(\w\+\)" # \(.*\)\(\d\+\w\+\).\*/\1"\3" # \4\2 key/
And yes, that's 100% unreadable, but it was easy to write with the preview mode on modern neovim.Although in this case I think a macro would probably be what I would settle on. This macro would look like:
^wdw/the<ret>wvawpwciwkey<esc>j (<ret> and <esc> are ^M and ^[ but I replaced them to avoid confusion)
But these are already the motions you would need to use to keep the multiple cursors in alignment. The thing is though that if you screw up it's usually easier to unscrew yourself when it's just one line than when you've gone ahead and screwed up 20 times. Yes it's still just an undo but when I've used multiple cursors, especially when you're trying to break up lines, you're forced to deal with that breaking up happening in all cursors. Things quickly get overwhelming.Multiple cursors are effectively (in my experience) just macro recording with automatic playback simultaneously at all the positions you care about. Whereas if you just record a macro and apply it wherever you would otherwise place your cursors, you get the same result without having the visual mess of dealing with multiple cursors. And it's just as error prone as macros.
As a simple matter of fact, I have been using vim since the mid-ish 90s every day and do exactly what you claimed don't really do.
I am proficient in emacs and don't need the mouse to do anything, but I am conscious I still reach for the mouse when more complex movements are required because it would be extremely dogmatic and unproductive to do otherwise, as it's just so much easier, and there's absolutely no shame in using the right tool for the job.
Please describe your non-roundabout way then.
How would you know that???
So that's the first roundabout eliminated:
- set word selection by default not to have to worry about substrings
But otherwise you're describing a different workflow: while there is no semantic meaning in the "next 5 lines", you actually don't know whether that would be 5 of 7, that's the whole point/benefit of incrementalism, there is in "this function", so block operations are a valid contender and I could also select the function and search&replace within the selection instead of doing word-by-word.
But then the original also works in a more direct way (maybe you actually don't want to replace all "foo" in a function, but skip some of them):
So you would skip your two roundabouts:
- go to the beginning of a function
- go+select to the end of a function
Then proceed with the "direct" way:
- go to your desired word anywhere within the function
- invoke select forward/backward commands until visually reaching the beginning/end of said function (the candidates will all be highlighted)
> How would you know that???
Typically I'm working on code that I wrote, or I've been studying it enough that I've gotten to the point that I'm refactoring and editing it. It isn't like a gameshow where I have a blinder on and then the blinder is removed and I'm given a task to perform.
> - set word selection by default not to have to worry about substrings
So you have to do another step? I can pick word selection or not dynamically on a case by case basis without having to go somewhere else and turn word mode on or off. Or maybe I'm misunderstanding how your editor works. At any rate, I don't see how my approach is roundabout -- I directly express if I want complete word matching or not, and I can even mix both types in a single search/replace operation.
I don't understand your next point -- I can select the body of a function without knowing how many lines it has. It isn't clear to me what your approach is, "incrementalism". Are you doing a word search and replace one at a time? I thought we were discussing multi-cursor editing.
> Then proceed with the "direct" way: > - go to your desired word anywhere within the function > - invoke select forward/backward commands until visually reaching the beginning/end of said function (the candidates will all be highlighted)
What you've described is just another way of doing it, not fewer steps.
That doesn't explain it. You can't know for any "foo" whether all the content, including comments, has "foo" as a subword for any non-trivial amount of text because you're not a computer.
> so you have to do another step?
No, you reduce many steps having to specify word-based selection every time with a cognitive overhead ("If there are other words") with a single default.
> I can pick word selection or not dynamically
So can I, and I don't need to do precision jumping in the middle of a combined text field to do that, but that's a different benefit
> It isn't clear to me what your approach is, "incrementalism"
when instead of counting 5 lines and selecting them you press a selection key the amount of times is needed to select what you need
> I can select the body of a function
and as I've pointed out, that's a different workflow. You've added a "in this semantic block" requirement and mistakenly compare it to the original incremental one
> just another way of doing it, not fewer steps.
it is fewer steps, I've pointed out which steps are not needed, and then there is another condition complicating your simple explanation when it's not a certainty that you actually want to replace everything within a block. But again, within semantic blocks there are different tradeoffs
>> I can select the body of a function > and as I've pointed out, that's a different workflow. You've added a "in this > semantic block" requirement and mistakenly compare it to the original > incremental one
Yes, I picked that scenario because doing search & replace on all vars of a given name across the whole file doesn't require selection. Yes, I would normally want to replace that var name when it appears in comments too. Yes, I typically have a very good if not perfect knowledge of whether foobar is a variable name only or might appear as a substring of something else. If my variable name was "n" then I'd have to take more care.
The original scenario explicitly states "not every instance in the open file ", so not sure why you bring that up
> in comments
same, where is this coming from? That adds no difference to these 2 workflows
> very good ... knowledge
This is just self-deception, you don't have the full text with auto-matching built into your brain, and since 'n' is an rethorical alternative, in reality you wouldn't even have a great way of knowing when to take more care
But also: how is your very serious "I know all the text" limitation relevant to this general comparison of 2 approaches?
Ages ago, Suresh Bhavanani (sp?) studied (suboptimum) usage of office and CADD applications. He was hired by the Army Corps of Engineers to help determine why adoption of CADD hadn't resulted in either higher productivity or improved quality.
His theory, which I believe, is various people have various "strategies" of varying merit. But for some reason users didn't (couldn't) readily learn better techniques from each other. IIRC, His two proposals were 1) re-organize user interfaces around tasks, instead of a features and 2) update training materials emphasize strategies.
IIRC, he compared manual and digital drafting. He identified a bunch of implicit and explicit "strategies" for manual work. eg Using ink and mylar, skilled drafters would work right-to-left, to avoid smudging prior work. Many early adopters carried those manual strategies over to CADD. Suboptimal, right? So then Suresh identified a bunch of digital appropriate strategies, leveraging the new tool.
Suresh did the same for spreadsheets.
Any way, this is a too long ramble now ending with the point: It'd be nice to see how others organize their work. I'm sure there's a lot of low hanging fruit "strategies". All the day to day tricks that experts use that don't get captured and explained in our training materials.
If I want to do that on 2-3 lines, I just use /findtext n and . to redo the prev command.
Anything more than that becomes a full search/replace.
Ymmv. It's just what I find efficient, given my muscle memory.
The mouse doesn't necessarily come into the discussion at all.
Relatedly, if you've created cursors across many lines (or more lines than fit in your viewport), and for whatever reason you want to move all the cursors to the start or end of lines, you want to press Home/End not once, but twice, due to to possible word wrapping on lines outside your view. I've lost nontrivial work by making this mistake!
Also in most use cases, clicking for each multicursor is the wrong way to do it.
With multicursor you can:
- Press a shortcut to auto select every instance of the characters that you already have highlighted (which is just s/foo/bar/g with visual feedback before confirming)
- Press your "select next occurrence" shortcut a few times so you're only changing the first few occurrences that you care about.
- Press your "select next occurrence" shortcut to select the instances you want and press your "skip next occurrence" shortcut to skip the instances you want to keep. That way you can change a bunch of "foo"s to "bar"s while keeping all "food"s as "food"s but also keeping those few instances of "foo" that you want to stay as "foo" (such as in comments, imports, tests, etc...)
- Press your "select next occurrence" shortcut (usually ctrl/cmd + d) each time if you literally want to see each instance before moving on to the next. Usually this is when you really want to make sure of something next to one of the occurrences.
- And finally clicking each cursor if there really is no simple pre-existing pattern for where you're trying to make changes
Sure, and if that’s the case you write a better pattern. The replacement s/foo/bar/g applies only to the current line as written. It’s easy to see if it will work or not. But if you also have food on the same line, you can write s/foo\ze\W/bar/g to match foo followed by a non-letter and replace only foo.
> Also in most use cases, clicking for each multicursor is the wrong way to do it.
Maybe, but I’ve sure spent a lot of time pair programming with people who do it, which is why I bring it up. For what it’s worth, I prefer multicursors, and my find-and-replace workflow is much as you describe, albeit entirely keyboard and regex driven. Your average junior programmer who just learned vscode three or four years ago only knows multiclicking though, and I don’t want to kill the momentum by interrupting to point out how they could use their tools better.
Suppose I'm editing a text file, and I'm working through a tradeoff between data volume and accuracy, where more accuracy requires more data. I have an array of 14 sensors, and I can configure them to take either 100-byte samples or 500-byte samples, and I can take samples at intervals between 1 and 60 seconds.
So I make myself a little table:
sensors size interval
14 100 1
14 100 30
14 100 60
Then I duplicate it: sensors size interval
14 100 1
14 100 30
14 100 60
14 100 1
14 100 30
14 100 60
I'm using kakoune, so I can put my cursor on the 100 in the first
duplicate row, press C twice, then press r5, and now I have this table: sensors size interval
14 100 1
14 100 30
14 100 60
14 500 1
14 500 30
14 500 60
Now, I want to know bytes per day, and this is where the math comes in.
I start adding columns. First I duplicate the three existing columns,
since I want to see them next to the result. I can do this by putting
a cursor at the beginning of each row, highlighting the whole thing,
and pasting. I also hit & to line up the pasted selections. sensors size interval
14 100 1 14 100 1
14 100 30 14 100 30
14 100 60 14 100 60
14 500 1 14 500 1
14 500 30 14 500 30
14 500 60 14 500 60
Now, because I don't want to have to think about the stack too hard,
I put 1440 (minutes per day) and 60 (seconds per minute) in before
the interval column. sensors size interval
14 100 1 14 100 1440 60 1
14 100 30 14 100 1440 60 30
14 100 60 14 100 1440 60 60
14 500 1 14 500 1440 60 1
14 500 30 14 500 1440 60 30
14 500 60 14 500 1440 60 60
Then I add my operations. And keep in mind I'm still using multiple
cursors, so all this stuff is just getting typed one time. sensors size interval
14 100 1 14 100 * 1440 * 60 * 1 / p
14 100 30 14 100 * 1440 * 60 * 30 / p
14 100 60 14 100 * 1440 * 60 * 60 / p
14 500 1 14 500 * 1440 * 60 * 1 / p
14 500 30 14 500 * 1440 * 60 * 30 / p
14 500 60 14 500 * 1440 * 60 * 60 / p
Now each line has a little dc program on it, like 14 500 * 1440 *
60 * 60 / p.I then highlight the back half of each row (again, still working with the same set of cursors the whole time, it takes way longer to explain this than it does to actually do it.) I type
| dc
and each of my selections gets run through dc. The result is: sensors size interval
14 100 1 120960000
14 100 30 4032000
14 100 60 2016000
14 500 1 604800000
14 500 30 20160000
14 500 60 10080000
That's not super readable, so I cursor over three times (multiselection
is still active!) and insert commas, and then I do it again: sensors size interval
14 100 1 120,960,000
14 100 30 4,032,000
14 100 60 2,016,000
14 500 1 604,800,000
14 500 30 20,160,000
14 500 60 10,080,000
The result is I've done a quick back-of-the-envelope calculation on
how much data I need to handle in each scenario.1. I wrote this comment using kakoune as the editor under w3m
2. I don’t know emacs as well as I’d like to. Anyone want to chime in on how you’d do this in emacs?
1. Be attractive
2. Don't be unattractive
Lack of multi cursor supprot is unattractive in current era.
Having both is even better. While I'm not a fan of Helix's selection→action model it's a great example of how macros&multicursors go very well together. Soon to be on neovim native too, so there's that
With that said, the reason live visual feedback haven't systemically replaced all previous modal interfaces, the way they have for media editing apps, is because it's easier to picture the result of a text editor (i.e., relative to a HSR change to a photo), so it's not quite as revolutionary for text editing as it is applied elsewhere, I'd still argue it's a universal good though, more visual feedback is always better.
But seriously though, why does everything these days need multiple cursors? It's a confusing visual gimmick in every scenario I've seen it implemented in. I'll take fully fleshed out structural regular expressions or even perl-re over multiple cursors any day. Combined with as vim's [c]onfirm flag you get all the benefits of multiple cursors without all the clunky downsides and weirdness.