reddit / emacs

My recent ramblings about multiple cursors and similar concepts

vifon · 2022-05-02 14:57
I'm not sure how insightful it is for a regular user, but maybe someone will find my thoughts on this matter an interesting read. Might also spark some interesting discussion. :) https://blog.einval.eu/2022/03/multiple-cursors-considered-suboptimal/
alternateved · 2022-05-02 15:23
Great article! Funny thing for me is, that this appeared exactly at the time, when I decided to ditch multiple-cursors in favor of `iedit`, `rectangle-mark-mode` and `kmacros`. The reason for that was that although multiple-cursors provide an intuitive way to multiedit, they tend to be slow, not so performant with higher number of cursors, undo does not work like you would want it to work and also those pesky "do you want to enable X?". Also, there is a really helpful overview of multiedit ways in [Meow's documentation](https://github.com/meow-edit/meow/blob/master/EXPLANATION.org#multiedit=).
andreyorst · 2022-05-02 15:59
For anyone who's interested in state of the art development in the multiple-selections world, I highly recommend trying [Kakoune](https://kakoune.org/). It's a Vim-like editor, so it may be foreign for most Emacs users, but what differs it from Vim is that it's built around the idea that you can operate on selections, selections selections, selections selections selections, and so on. For example, in multiple-cursors.el there's a function `mc/mark-all-in-region-regexp` which can select several regions that match a regexp inside currently active region. However it's not possible to do another match inside resulting regions with this function - it will only operate on the very first region. Kakoune allows this, and also all kinds of selection manipulation primitives alongside that. Additionally to that, Kakoune has a `map` variant that maps a certain command over all selected regions, so you can not only preform your regular shortcuts, but also do it programmatically. A common thing about multiple cursors and keyboard macros, that a lot of people, for some reason, think that they're mutually exclusive, which isn't true at all. Kakoune, being focused on multiple-selections, still has a keyboard macro system, similar to that in Vim. But it integrates all multiple-selection features into that system, so its much more powerful. Emacs can do that too, thankfully, (though I remember some bugs I had with that)
ndamee · 2022-05-02 16:34
> However it's not possible to do another match inside resulting regions with this function - it will only operate on the very first region. It is possible, only nobody has implemented it yet, so it's mainly a matter of motivating people to write it or doing it yourself. You could, for example, post a separate submission about this feature describing how it is useful to draw attention to it, and if people hear about it and see the potential then someone may implement this for multiple cursors.
divinedominion · 2022-05-02 17:19
Technical background: multiple cursors don't work well with Emacs's "gap buffer" model to text editing. https://nullprogram.com/blog/2017/09/07/ Changing a buffer to insert e.g. 10 characters in multiple places at once requires the gap buffer memory model to:[#20210213muc][] 1. insert char 1 at position 1 2. move memory of gap buffers when moving from position 1 to 2 3. insert char 1 at position 2 4. move memory of gap buffers when moving from position 2 to 3 5. insert char 1 at position 3 6. Repeat with characters 2, 3, 4, ... 10 The gap buffer memory movement is the culprit: > For an edit of length `n` with `m` cursors, that’s `O(n * m)`` calls to `memmove(3)`. Multiple cursors scales badly. (https://nullprogram.com/blog/2017/09/07/) Macros can insert all 10 characters at once per invocation per line.
Alan_Shutko · 2022-05-02 17:36
That's a good point. Especially if you combine kmacros with registers, you can do a lot of complicated transformations. I've done things like pull specific stuff out of log files and transform them into json for a later process.
snippins1987 · 2022-05-02 17:42
I heavily use both multiple-cursors and keyboard macros. In fact, I regularly use both together. There are indeed lots of work is needed to make the Emacs's version of multiple cursors play nice with other things like search, auto complete, kakoune's features parity, and making it easy to "undo" after "reckless" movements. But it Emacs, so I eventually achieved all of that, and I would say your complaint is more about the default experience that the Emacs package brings, and not really a problem of the concept itself. For cases where you want to make changes to a single file, however, I would argue that multiple cursors can achieve features parity with macro while using a lot less brain power. Kakoune is a very good example to show that such an implementation is possible. Kmacro, on the other hand, is god sent for cases involve buffer navigation. For example, I often record a macro that open a file in a dired buffer, make changes to that file using multiple cursors, go back to dired, move to the next file and end macro recording, then apply the same changes to other files. I think everyone should try a bit of Kakoune or Sublime (to a much lesser extent) to see how good of a replacement that multiple cursors can be for single file editing.
andreyorst · 2022-05-02 20:32
> You could, for example, post a separate submission about this feature describing how it is useful to draw attention to it, and if people hear about it and see the potential then someone may implement this for multiple cursors. I'd rather not. After Magnar Sveen closed the issue tracker in the package, there's no central place to express such suggestions. They doesn't want neither to solve other's people problems, nor to host a tracker for them so other people could solve those. There's another project, though: https://github.com/knu/mc-extras.el This project has some (IMO essential) features, that for some reason didn't make it into the multiple cursors original package, like `mc/remove-duplicated-cursors` which merges cursors standing on each other into a single cursor, to avoid multiple cursors inserting into the same place several times, which happens a lot to me, but Kakoune does it by default, so it never happens to me there. If at all, I'd like a new package for multiple cursors to appear, perhaps with a better performance, and a bit more open maintainer.
_noctuid · 2022-05-03 00:37
> I would argue that multiple cursors can achieve features parity with macro while using a lot less brain power The visualization is nice but doesn't seem that important to me. Once you have more than just a few visible cursors, I don't see how it is helpful anymore. You can't actually watch all edits (even if in a single file only), especially if you have hundreds cursors elsewhere in the file. Multiple cursors don't scale. Macros do and are more flexible. Of course you can use both if you want to, but I personally prefer to just use macros.
_noctuid · 2022-05-03 01:10
> However it's not possible to do another match inside resulting regions with this function - it will only operate on the very first region. Kakoune allows this, and also all kinds of selection manipulation primitives alongside that. What does calling this again do? Does it narrow the selections, or are you saying it makes a nested selection (and if so, what is that)? Could you give an example use case? Kakoune's multiple selection seems incredibly interesting (vis also), but I don't seem to run into many situations where they would be much more useful than macros. This may of course just be because I don't have enough experience with multiple selections to recognize cases I could handle better with them.
snippins1987 · 2022-05-03 04:07
You actually can easily watch all the edits with: mc/cycle-forward mc/cycle-backward About scaling, this is my way to make multiple-cursors editing scales with Emacs Gap Buffer, by leveraging ... kmacro. When I run something that require to generate more than 200 cursors, then it would automatically save the cursors positions (using markers) for the 201st onward, ask me to start record a macro (LOL). After done editing the first 200 cursors, I save the macro and from there I have 2 shortcuts that: + Apply the Kmacro gradually for each remaining cursors. + Apply the Kmacro for all remaining cursors. And just like that, you only need 2,3 more keypresses to make multiple-cursors scales linearly. Personally, I have nothing against using only Kmacro, I just want to address that the commonly mentioned "limitations" of multiple cursors are not at all impossible to overcome, even with Gap Buffer. And funny enough, in Emacs case, that's precisely because of the powerful Kmacro.
_viz_ · 2022-05-03 06:08
I come from a multiple-cursors heavy editor---vis. Before using vis, I was a vi/vim user and that means I was taught to use macros and ex commands instead of relying on the "phoney" multiple cursors paradigm; I complied as their answer seemed to work good enough. But vis changed my mind: it beautifully combines the user-friendliness of multiple-cursors and the expressiveness of the sam(1) command language. You can think of sam command language to be ed(1) on steroids. vis lets me write "partial commands" to make multiple-cursors based on it. E.g., I can write :x/Emacs/c/vis if I want to change every instance of Emacs to vis but I can write :x/Emacs RET which would select every instance of "Emacs" in the text file. Then I can move around however I want and do my editing all at once: this is excellent visual feedback here. Although the example is very naive, it already addresses one of the main gripes of multiple-cursors (IMO, at least): _creating them_. Since there's an organic transition between the familiar sam command language and multiple-cursors, you do not have to "learn anything new," you just start using it. Another alternative that people often quote is visual block (or rectangle-mark-mode): but that is the most annoying thing ever! With multiple-cursors, you can have a _discontinuous_ "region" which is a lot more user friendly than ditching rectangle-mark-mode and going with query-replace-regexp or somesuch just so there's a single line in the middle that you do not want to modify! I agree with you that multiple-cursors are intuitive. Although I'm happy with query-replace-regexp (and especially being able to run arbitrary lisp forms in the replace part), I still long for vis' multiple-cursor functionality. For some time, I was using vidir+vis to bulk-rename files, and not dired for a reason. P.S. I have not tried the multiple-cursors package since I could never find the same "organic transition" I spoke of.
_viz_ · 2022-05-03 06:15
Not sure about what you mean by "multiple selections" but I liked that in vis I could narrow my selection down progressively and get immediate visual feedback at the same time. From here, I could either use multiple-cursors or the sam command language to edit my selections: so you tend to pick whatever is the best for the job. For quick edits, I go with multiple-cursors but for longish ones I go with sam command language. I like the visual feedback as I do not use the sam command language to its fullest often (same with Emacs' macros) so I forget the nuances of these features and its "killer techniques" so having a familiar fallback is nice when you just want to get things done. EDIT: The best comparison I can think of is writing Elisp! I think most people write Elisp "progressively" in that they write small units first, then eval the sexp to see if they got it right and so on. In a very similar way, I was able to write my complex expression by parts with visual feedback along the way: if I got too bored or am impatient, I fell back to using multiple-cursors.
andreyorst · 2022-05-03 11:38
> Not sure about what you mean by "multiple selections" Kakoune doesn't have a cursor at all, it's just always a 1-character selection, it can insert before selection, after selection, expand this selection during movement, shrink it, filter, and so on. It's like a permanently active visual mode in Vim.
vifon · 2022-05-03 11:40
Somehow I never stumbled upon this post. It was a great read, thanks!
andreyorst · 2022-05-03 11:52
Just for illustration, not a very practical example. Say, you have this text: double foo = 0.123; double bar = 123.456; double baz = 0.133; You can select it as a paragraph (selection is represented with `[]`): [double foo = 0.123; double bar = 123.456; double baz = 0.133;] Then you can press `s` and select all digits with a regexp `[0-9]+\.[0-9]+`: double foo = [0.123]; double bar = [123.456]; double baz = [0.133]; If you hit `s` one more time, you can select all decimals with another regexp `\.[0-9]+`: double foo = 0[.123]; double bar = 123[.456]; double baz = 0[.133]; Repeating all these steps in Emacs currently gives this result: double foo = 0[.123]; double bar = 123.456; double baz = 0.133; Yes, you can do this in a single selection, but that's beside the point. Kakoune allows you to do much more things with selections in-between operations, for example filtering selections that doesn't start with `0.`. The main point is that you can do it step by step, decomposing problem into small incremental selections.
_noctuid · 2022-05-03 13:31
From my perspective, this is just using macros, and you are incidentally using the multiple cursors package to mark places to apply them (at least you can say this is not the normal workflow when people talk about multiple cursors). Well in your case it may just be a performance workaround (?), but for me, I may sometimes want to check that each individual edit works correctly. You could potentially do things this way without ever showing multiple cursors. But that said, this is exactly how I would personally use multiple cursors (as a kmacro enhancement/a sometimes better way to select locations to run macros *either* incrementally or all at once). Can you share your config for this? The problem is that I use evil and may not be able to replicate with evil-mc.
snippins1987 · 2022-05-03 14:13
Yes you could say that, but I personally really need the interactive aspect and the first 200 cursors really help me make to reduce mistakes (like 99.99%). The code below is from my own version of multiple-cursors, but you can evaluate these to override the corresponding version of the original package. Since there are a lot of function I need to override to support this workflow, I'll just show how I changed mc/mark-all-like-this to work with it. Also in my experience applying complex kmacro over 7-8000 times would make Emacs choke anyway, in that case I use sed, awk, etc. via my modified version of "shell-command-on-region" or ... Kakoune. Kakoune is very, very fast, I once use it and create 300k cursors with no lag whatsoever. It's my secondary editor for these rare cases. (setq mc/max-cursors 201) (defun mc/create-fake-cursor-at-point (&optional id) "Overriding mc/create-fake-cursor-at-point to save hidden cursors" (unless mc--max-cursors-original (setq mc--max-cursors-original mc/max-cursors)) (if mc/max-cursors (if (< (mc/num-cursors) mc/max-cursors) (let ((overlay (mc/make-cursor-overlay-at-point))) (overlay-put overlay 'mc-id (or id (mc/create-cursor-id))) (overlay-put overlay 'type 'fake-cursor) (overlay-put overlay 'priority 100) (mc/store-current-state-in-overlay overlay) (when (use-region-p) (overlay-put overlay 'region-overlay (mc/make-region-overlay-between-point-and-mark))) overlay) (if (use-region-p) (progn (setq-local my-mc-hidden-cursors (cons (cons (point-marker) (copy-marker (mark-marker))) my-mc-hidden-cursors))) (setq-local my-mc-hidden-cursors (cons (cons (point-marker) (point-marker)) my-mc-hidden-cursors)))) (let ((overlay (mc/make-cursor-overlay-at-point))) (overlay-put overlay 'mc-id (or id (mc/create-cursor-id))) (overlay-put overlay 'type 'fake-cursor) (overlay-put overlay 'priority 100) (mc/store-current-state-in-overlay overlay) (when (use-region-p) (overlay-put overlay 'region-overlay (mc/make-region-overlay-between-point-and-mark))) overlay))) (defun mc--maybe-set-killed-rectangle () "There are some bugs regarding saving to kill ring for rectangle editing, but I don't use it, so just ignore it") (defun my-apply-macro-for-the-next-hidden-cursor () "Apply kmacro to the next hidden cursor" (interactive) (let* ((next-cursor (car my-mc-hidden-cursors)) (p (car next-cursor)) (m (cdr next-cursor))) (if (not (= p m)) (progn (push-mark m t nil) (goto-char p) (activate-mark)) (goto-char p)) (call-last-kbd-macro) (setq-local my-mc-hidden-cursors (cdr my-mc-hidden-cursors)))) (defun my-mc-remove-current-cursor () "blabla" (interactive) (let ((old-point (point))) (when (not (call-interactively 'mc/remove-cursor-at-point-if-exist)) (if (mc/last-fake-cursor-before (point)) (call-interactively 'mc/cycle-backward) (call-interactively 'mc/cycle-forward)) (setq-local new-point (point)) (goto-char old-point) (call-interactively 'mc/remove-cursor-at-point-if-exist) (goto-char new-point)))) (defun my-apply-macro-for-all-hidden-cursors () "Apply kmacro to all hidden cursors" (interactive) (while my-mc-hidden-cursors (let* ((next-cursor (car my-mc-hidden-cursors)) (p (car next-cursor)) (m (cdr next-cursor))) (if (not (= p m)) (progn (push-mark m t t) (goto-char p)) (goto-char p)) (call-last-kbd-macro) (setq-local my-mc-hidden-cursors (cdr my-mc-hidden-cursors))))) (defun mc/mark-all-like-this () "example: override mc/mark-all-like-this to make it work with hidden cursors" (interactive) (unless (region-active-p) (error "Mark a region to match first.")) (mc/remove-fake-cursors) (setq-local my-mc-hidden-cursors nil) (let ((master (point)) (case-fold-search nil) (point-first (< (point) (mark))) (re (regexp-opt (mc/region-strings) mc/enclose-search-term))) (mc/save-excursion (goto-char 0) (while (search-forward-regexp re nil t) (push-mark (match-beginning 0)) (when point-first (exchange-point-and-mark)) (unless (and (= master (point))) (mc/create-fake-cursor-at-point)) (when point-first (exchange-point-and-mark))))) (if (> (mc/num-cursors) 1) (multiple-cursors-mode 1) (mc/disable-multiple-cursors-mode)) (when my-mc-hidden-cursors (my-mc-remove-current-cursor) (setq-local my-mc-hidden-cursors (reverse my-mc-hidden-cursors)) (message "Visible cursors threshold is hit, hidden cursors created, please record macro to apply changes to them")))
_noctuid · 2022-05-03 14:44
Thanks for the explanation. So this allows you to progressively refine the selection, changing the number of selections as you go and removing invalid cursors. This has some overlap to a feature I've wanted in Emacs for a long time for other reasons and previously hacked together a very slow implementation for: discontiguous narrowing, which can be very useful for more complex text object definition (particularly when treesitter is unavailable). This does seem like it could be very useful, but I've had trouble in the past when looking at kak of finding practical examples. Especially when it comes to using regexps for things in any editor (though that's just for this example), I haven't found many cases where they seem like the best solution besides search/replace. I'm interested in multiple selections in other editors because I am (slower than a snail's pace at this point) writing a better text object system. It will need to handle things that are composed of multiple discontiguous regions (e.g. misaligned columns) and should support returning a bunch of region bounds for the selection of multiple things, so that it can be used by a package that handles multiple cursors/selections. My package could, for example, return all objects matching a regexp in a region. A multiple selections package could then give it back that collection of constraint regions with a new regexp, and my package could return new regions within each constraint region for matches. Ideally, I want to be able to support whatever it makes sense to wrt. multiple selections, but I'll have to understand their use cases better first. Vimgolf examples often seem to be contrived and not all that useful. Let me know if you are aware of any good example videos of someone using kak's multiple selections or examples of more common situations where they are uniquely useful.
meedstrom · 2022-05-04 18:48
Could I ask you to print the full sequence of keys you use in each video? I learn best from that. It's fine if some of them are custom, maybe you can say e.g. "M-g f is mc-forward-something, and M-g c is mc-foobar".
vifon · 2022-05-05 16:09
I should have done it to begin with. Point taken for the future. Let me try to recreate these scenarios now… First scenario: - `C-s :straight C-s RET` - `M-b` - `C-M-SPC` - `C-<` and `C->` a few times (custom: `mc/mark-previous-like-this` and `mc/mark-more-like-this-extended` respectively) - `C-w ensure` - `C-g` The second one is very similar but with additional movement before the part when I edit stuff: - `C-s :straight C-s RET` - `M-b C-b` - `C-M-SPC` - `C-<` and `C->` a few times (custom: `mc/mark-previous-like-this` and `mc/mark-more-like-this-extended` respectively) - `C-g` (or maybe `C-SPC`? Essentially I wanted to turn off `transient-mark-mode`) - `2*M-f` `C-f` - `C-d` - `C-y` - `C-g` The last scenario does what the second one did, but with a kmacro: - `C-s :straight C-s RET` - `C-u C-M-:` (custom: `kmacro-x-mc` from my [kmacro-x](https://github.com/vifon/kmacro-x.el)) - `M-b` - `C-M-SPC` - `M-w` - `2*M-f` `C-f` - `C-M-k` - `C-y M-y` - `M-<` - `C-x C-k C-k` followed by a few more `C-k` to repeat the kmacro more times There are slight variations in terms of how I kill & yank text around, but the second and third cases do more or less the same thing.
rdt-usr · 2022-11-21 17:30
[Emacs 'zones'](https://www.emacswiki.org/emacs/Zones) Would this be helpful?
rdt-usr · 2022-11-21 17:32
[Emacs 'zones'](https://www.emacswiki.org/emacs/Zones) Wonder if this would be helpful.
rdt-usr · 2022-11-30 08:12
Also see: [Gap Buffers Are Not Optimized for Multiple Cursors](https://nullprogram.com/blog/2017/09/07/)