lispy
lispy copied to clipboard
Feature Request: Teleporting to beginning/end of S-exp
(1
2
3)
|(a) (b) (c)
I think it'd be cool to be able to teleport an s-expression to the end of another s-expression.
(1
2
3
(a))
(b) (c)
Similarly, it might be cool to support teleporting to the very beginning of an s-expression.
(1
2
3)
(a) (b) (c)
to
((a)
1
2
3)
Teleport to end of the previous s-expression: press ok. Move from the end of the s-expression to the beginning: press 9w or 99w.
Also check out oj, oh and ol.
Hmnn, I didn't know about other-mode's functionality. That's pretty cool.
I think I usually have this usecase where there's a lot of s-expressions between where I want to teleport to.
e.g.
(1
2
3)
(a) (b) (c)
((a)
1
2
3)
|(b) (c)
to
(1
2
3
|(b))
(a) (b) (c)
((a)
1
2
3)
(c)
This is still doable with something like wwwwwwok.
Thanks, not ideal but I'll work with that. :)
Sometimes I swap around top level sexps (for which w and s are fine), but I've never encountered something like your example (maybe a concrete example would make the use case more clear).
Fwiw, what you want should be achievable in a more generalized way with a combination of evil-exchange (for the exchange operator), lispyville (for a sexp text object), and targets.el (for making a "remote" version of the sexp text object, i.e. it is selected with avy). Lispyville's sexp text object may be a little buggy though, and I'm in the process of rewriting targets.
To clarify, evil-exchange isn't the correct operator, but it would look pretty similar to write one for the case of sexps.
For my use cases, at least for the teleport action, I almost never want the s-exp to go where it goes now. Usually it going at the end is more clear (at the beginning would be nice too, of course).
One example I can think of if teleporting a big expression at the end of a let statement to become a new body.
Lets say you want to teleport a nested expression into another s-exp farther away (say a (let (()) [where you want the sexp to go]). Pressing w a few times will be undesirable as you'll be morphing your code several times to get it to where it needs to go.
My approach right now for that is just to kill the s-exp, navigate to the let, and yank the text into the buffer. That's closer to the atomic action I'd want without modifying (changing undo history/etc) the rest of the buffer.
Any chance you remember or could create a real example in a code block?
I think the generic solution is to have a teleport operator (similar to evil exchange except you are "exchanging with an empty position" or moving the initial selected area to the beginning of the second motion or object). Lispy could also provide global avy commands (i.e. they would work anywhere in the visible window). The core library for targets could potentially be used for either. Even if not added to lispy, it shouldn't be hard to do independently.
If you only care about beginning and end positions or anything more specific like that, my library has a filtering mechanism to remove "invalid" overlays.
@noctuid I don't have anything concrete in mind. I usually encounter this want when writing new functions in scratch where I'm iteratively building up the final function.
;; Example.
(let ((a (1))
(b (2))
(c (3))
[Want to keep adding to let binds.])
(body))
(scratch)
Lispy could also provide global avy commands (i.e. they would work anywhere in the visible window). The core library for targets could potentially be used for either. Even if not added to lispy, it shouldn't be hard to do independently.
If you only care about beginning and end positions or anything more specific like that, my library has a filtering mechanism to remove "invalid" overlays.
This sounds good to me.
A more concrete example:
(defun j|prefix-modeline ()
""
(let ((F mode-line-format)
(modeline-segment )))
(unless (member |'(:eval (j|modeline-background)) mode-line-format)
(cons (car mode-line-format) (push 10 (cdr mode-line-format) ))
)
)
to
(defun j|prefix-modeline ()
""
(let ((F mode-line-format)
(modeline-segment |'(:eval (j|modeline-background)))))
(unless (member mode-line-format)
(cons (car mode-line-format) (push 10 (cdr mode-line-format) ))
)
)
@jojojames Your last example is a prime use case for lispy-bind-variable. The user experience on that one is not fully polished yet:
- it will always introduce its own
letexpression - you'll have to combine M-m, hC to move this
letexpression to where you want
Currently, there's no way to merge two let expressions. But you can teleport a variable binding from one expression into the other and then delete it.
Maybe it could be a feature of lispy-teleport that when it teleports out the last variable binding out of a let, it will also eliminate the let wrapper.
Let me know what you think. Ideas welcome, but please use a new issue in that case. I'm also interested to hear what @noctuid thinks about xb.
@jojojames If you haven't already typed what you want to move, I think you'd be better off using lispy, avy, or some motion to get where you want first. I do think that a teleport operator could be potentially useful though.
@abo-abo I think that lispy-bind-variable is a great idea, and I've used it a few times before. In some cases it works fine, but the issues you mention do make it difficult to work with for other cases. Your suggestion for lispy-teleport to delete an empty let would be nice and the simplest way to handle things. I think it would be even better if lispy-bind-variable (optionally) just put the binding at the end of the closest (or maybe nth closest) enclosing let.
This could be done the dumb/wrong way pretty easily (e.g. search within the current top-level form for the closest (let whose list encloses the area xb is used on). Even though this might work 99% of the time, it really shouldn't be done like this.
Unless Emacs already has some way to tell whether a symbol at point is actually a macro/function/special form call (and I'm not aware of anything that does that), a lot more witchcraft would be involved. You'd have to do something like use cl-macrolet to temporarily bind let to expand to let with the extra binding, but that wouldn't work. I'm sure it's not allowed to rebind builtin special forms, and since elisp doesn't have pakages, you couldn't do this in a clean namespace and expand to something like emacs-lisp:let. I'm not aware of any way to do code walking in Elisp (I'm pretty sure there's no equivalent of something like sb-walker:walk-form from SBCL, for example), so doing this correctly would probably be a nightmare.
Edit: I have some thoughts about a hacky way to do this for elisp, but it probably isn't worth doing. There are a lot of reasons that doing this the "right" way would suck (mainly that for each lisp you want lispy-bind-variable to work for, you'd have to use that lisp's specific method of code walking if it even has one, and the solution would potentially be pretty convoluted depending on the lisp).
@abo-abo I think teleporting the binding to an existing let and removing the old one is probably the best solution after all. Another thing to consider is that there are a ton of let variants, and if the user does the teleporting, lispy doesn't have to worry about any of that either.
@jojojames Your last example is a prime use case for lispy-bind-variable. The user experience on that one is not fully polished yet:
it will always introduce its own let expression you'll have to combine M-m, hC to move this let expression to where you want
I don't actually use lispy-bind and other more ide-like functions, due to various reasons.
- Still learning lispy
- Subtleties like above^
- My head works better with lower level primitive actions
Having said that, of course this could be a chance to improve. :)
Maybe it could be a feature of lispy-teleport that when it teleports out the last variable binding out of a let, it will also eliminate the let wrapper.
That might be cool but I'd be happy with just a dumb teleport, honestly it'd be cool if we could just teleport anywhere in an s-expression.
@jojojames If you haven't already typed what you want to move, I think you'd be better off using lispy, avy, or some motion to get where you want first. I do think that a teleport operator could be potentially useful though.
Yeah, lots of time I've already typed out the expression.
I'll reopen this topic for now since we're still commenting but happy to close if a teleport-to-end isn't a good idea afterall. :)