Open chip2n opened 2 years ago
Definitely, and other users have brought this up before as well. This falls under properly supporting Evil's "Visual mode" which is a Vim-like modal way to use regions.
For now, you could use commands like c
/x
/y
along with quantifiers (e.g. 3c
) to account for some of these cases, and C
/D
/Y
in other cases. But I agree that proper support for visual mode / regions would be valuable and can't always be expressed this way (not to mention visual feedback is nicer than relying on counting correctly!).
I seem to recall I did initially try to add support for it a long time ago, but at the time I wasn't familiar with evil and wasn't able to get keybindings to be recognized in Visual state. Were I to attempt it now, I would try something like this:
symex-editing-mode
), where the motions (e.g. hjkl
) move to the end of each expression instead of the start (as it usually does in Symex mode).symex-editing-mode
map for Normal and Operator state, but use the new visual-specific keymap for Visual state (probably would need to add a separate visual-specific helper there).f
, b
or k
(i.e. the default k
, unless you've remapped the up/down orientation)), we should probably make them no-ops in the Visual state keymap, i.e. they should have no effect.cdy
. Since they are just operating on regions at this point, we can use standard utilities for this. That is, instead of doing symex-delete
or symex-change
they would just do evil-yank
, evil-change
and evil-delete
(or even the equivalent Emacs functions like delete-region
, if those would be more appropriate or convenient). Other useful commands like HL
to shift the region around could also be added, but these would probably require writing custom functions like symex-shift-region-forward
(or we could potentially use evil-delete
(or delete-region
), followed by an appropriate motion, and then evil-paste
), so they could be added as a followup and needn't be part of the initial effort.Now in visual state we can already use o
to switch to the "other" side of the region (you could try this in Evil's Normal state). As you pointed out in Lispy's docs, this is a useful feature to be able to expand/shrink the region in either direction. In order to support this, we would now need to modify the motion functions we wrote in step (1) to condition on the side of the region on which the cursor currently is. That is, if the cursor is at the end, then do the behavior we have already defined, otherwise if it is at the start, then we move to the start of each expression (similar to Symex's current way) instead of the end.
Assuming all that works out, this would be quite a nice addition.
Thank you for the detailed response! I might take a stab at this and open a pull request if I have something that seems interesting. Here's some of my initial thoughts:
Regarding point 3, I think I'd expect the region to follow the point even when moving to deeper levels of nesting. So if I press j
followed by k
, I end up with the original region (like how symex usually lets you "revert" to the previous position by using the opposite keys). Or, to put it another way, the marked region is always around the current expression until the user grows it to extend it beyond that.
Growing/shrinking the region could perhaps just use the default slurp/barf bindings in the visual mode? In that case, we don't need to consider which side of the expression the cursor is on.
Ah, I think I understand now. We are describing two different paradigms here 🙂
The first is essentially a "point-free" paradigm, where we don't have a notion of the point/cursor position and it is entirely in relation to the current region as a whole.
The second is a motion-oriented one where the point guides the boundaries of the region.
To illustrate the difference, if we wanted to go from this selection:
(a b c [d] e f g)
to this one:
(a [b c d e f] g)
it would take 4 steps in the pointfree way, and 5 steps in the motion-oriented way (including the o
to move point to the other end). I do agree that the former is even more convenient in this case than the numbers would seem to indicate.
On the other hand, to go from:
(a b c (d e f (g h i (j k [l]))))
to this:
(a b c [(d e f (g h i (j k l)))])
With the barf/slurp (or "emit/capture" as they are called in symex) bindings, I assume this would do more than just go back and forward at the same level? That is, it would probably expand to larger and larger expressions when it hits a boundary at the present level? Then it would take 11 steps to reach the (d ...)
expression.
With hjkl
motion-oriented regions, it would take 3 steps -- just "out, out, out".
My feeling is that the motion-oriented way is more general than the pointfree way, and it should be possible to implement the latter on top of it as a thin wrapper. That way, we could support both simple motions like hjkl
as well as emit/capture to "do what I mean" in many common cases, and could even use both paradigms at the same time for maximum flexibility since there is no conflict between them.
Re: point 3, yeah I think you're right, and we should support entering expressions as long as we are able to avoid selecting things that are structurally invalid like (a [b (c] d) e)
. To go over some example cases to help in deciding what should happen:
(a [(b c)] d)
could yield
(a ([b] c) d)
regardless of point location, while
(a [|(b c) (d e)])
(note point on the left end) could yield:
(a ([b] c) (d e))
and
(a [(b c) (d e)|])
(point on the right end) could yield:
(a (b c) ([d] e))
and that may avoid any weird cases of non-structural selection.
Yep, it sounds like we're on the same page! I'll try to set aside some time this week to look closer at it :+1:
Does this need to be tied to evil's visual mode? Could we just use region, simular to meow?
@devcarbon-com You may be right that it would be better to use regions directly. The design discussed above does seem to introduce the idea of a "motion" in the Evil sense, but I'm not sure if it sufficiently fits into Evil's UI paradigm where it would be much easier to implement in evil vs doing it directly using regions. And of course, doing it with regions directly avoids increasing the dependency on evil. If we used regions directly though, we'd still need to ensure that it is "modal" in some way, so that e.g. pressing y
after selecting some code copies it, and c
changes it, instead of inserting y
and c
as it naively would with regions.
@chip2n, have you done any work on this? I'm considering giving this a go if I find time in the near future.
@chip2n, have you done any work on this? I'm considering giving this a go if I find time in the near future.
I never got the time to do this unfortunately, so please go ahead! I think it would be a great addition to this package.
One feature I'm missing since switching from lispy is the way you can operate on regions, in particular the way you can mark the current S-expression and then grow/shrink the region.
https://github.com/abo-abo/lispy#operating-on-regions
Is this in scope for the project?