ggandor / leap-spooky.nvim

👻 Actions at a distance
The Unlicense
277 stars 7 forks source link

[feature request] create operator like yr dr cr etc. #6

Closed singlexyz closed 1 year ago

singlexyz commented 1 year ago

For a keymap like darw<leap>, many users should have never tried to insert a key between a and w, and most people should press aw directly.

If use the operator, so that aw/iw can be reserved, it should be more convenient to use.

Like draw<leap>, At the same time, it can be used in combination with custom textobj.

Gazareth commented 1 year ago

Are you talking about how people's muscle memory will favour aw/iw over arw/irw due to familiarity with the native vim text objects? If so I'd agree and I think it would be good to see this.

danielo515 commented 1 year ago

Completely agree here. The way I think about the motions is first action (yank, delete, change), then scope (around, till, forward...) and finally text object. Often times I see myself thinking about "delete remote around word", but that is indeed not the right thing, and I end being frustrated 😅

ggandor commented 1 year ago

To be clear: we are creating text objects (although slightly magical ones, with side effects), not operators. If the core team would add a new operator to Neovim tomorrow, that would work with spooky automatically. If I understand correctly, @singlexyz, you wish for a completely different implementation that turns the whole thing inside out? I'd find that approach much less intuitive.

Spooky is just an extension of targets.vim's logic. Yanking the "next" word or a "remote" word is still a yank operation, we just want to operate on a special argument that is not the default one of its kind (under the cursor).

More generally, any operator operates on a range of text, that is, expects a start and end position in the buffer, period. It would make little to no sense to differentiate between operators based on some arbitrary property of their input.

Since and we're not talking about "yanking remote-ly", but a property of a text object, "around remote foo" follows natural language more closely than "remote around foo", and also follows the convention established by targets.vim (inw as "inner next world", etc), that is why I chose this order.

Gazareth commented 1 year ago

Since and we're not talking about "yanking remote-ly", but a property of a text object, "around remote foo" follows natural language more closely than "remote around foo",

Why aren't we talking about "yanking remote-ly"? I feel like this comes down to a matter of perspective. I, for one, interpreted this plugin as empowering the operators (y, d, c, etc.) in order to allow them to act 'globally'; on text objects not within the vicinity of the cursor.

and also follows the convention established by targets.vim (inw as "inner next world", etc), that is why I chose this order.

I'd argue that using conventions set by targets.vim is going down a path contradictory to this plugin's nature - because there's no such thing as a 'next' or 'last' text object when you're effectively everywhere in the buffer at once (spooky).

ggandor commented 1 year ago

Why aren't we talking about "yanking remote-ly"? I feel like this comes down to a matter of perspective.

The plugin defines visual and operator-pending mode commands that select a region, and as such, can be used by (any) operators. That is the definition of a text object in Vim lingo (:h text-object). That is why it's not "[remote-yank] an {object}", but "[yank] a {remote object}". A remote region of the buffer is still just a region like any other, that is, a pair of positions.

I'd argue that using conventions set by targets.vim is going down a path contradictory to this plugin's nature - because there's no such thing as a 'next' or 'last' text object when you're effectively everywhere in the buffer at once (spooky).

My point was about the scheme that targets.vim uses: inner/around - location - text-object. We generalize this scheme by replacing the location part ("next"/"last", optional count) with a universal r prefix ("anywhere but here, specify with leap motion").

The order could be switched, but targets.vim already puts the location specifier after a/i, and as I mentioned, it also follows natural language better (at least "inside remote block", "around remote block", "a remote block" - for "inner" it doesn't really matter),

Gazareth commented 1 year ago

Again I think this depends on perspective. either leap spooky creates its own text objects that are 'remote' versions of existing/built in text objects. Or, leap spooky adds its own operators which can act on existing/built in text objects from afar.

I would put forth the case that given how leap-spooky's behaviour is so radically different to usual operator/text object manipulation (i.e. having to type to find targets, then select one via the labels), it makes more sense that it uses separate operators that require extra inputs, rather than the text objects themselves triggering these extra listening functions off the back of them being selected.

But again, not sure we will get a resolution on this one since it could just be a difference in thinking -- would you be open to having this as a configurable option of leap-spooky.nvim?

ggandor commented 1 year ago

Again I think this depends on perspective. either leap spooky creates its own text objects that are 'remote' versions of existing/built in text objects. Or, leap spooky adds its own operators which can act on existing/built in text objects from afar.

We're talking past each other: yes, we could define special operators (that would work with any text object in exchange, even with custom ones, out of the box), but that is not what happens now, in the current implementation - that would be a from-scratch rewrite, another plugin if you like. Is that what you propose?

All I'm saying is that considering what this plugin actually does, irw makes a tiny bit more sense than riw.

would you be open to having this as a configurable option of leap-spooky.nvim?

You mean just making the mappings configurable, in the current implementation? (As I said, defining operators is practically another plugin.)

ggandor commented 1 year ago

I would put forth the case that given how leap-spooky's behaviour is so radically different to usual operator/text object manipulation (i.e. having to type to find targets, then select one via the labels), it makes more sense that it uses separate operators that require extra inputs, rather than the text objects themselves triggering these extra listening functions off the back of them being selected.

I don't necessarily agree. As I said, ultimately we're selecting a region, who cares by what means we're achieving that? iw is just as magical as it moves the cursor to the beginning of the word and then selects it till the end, there's nothing fundamentally different in what we're doing. The only really weird side effect is the cursor bouncing back to the start position in case of r/R objects.

ggandor commented 1 year ago

Also keep in mind that Spooky wouldn't work in Visual mode, if it would define its own special operators, instead of text objects. (By definition.)

Gazareth commented 1 year ago

ultimately we're selecting a region, who cares by what means we're achieving that? iw is just as magical as it moves the cursor to the beginning of the word and then selects it till the end, there's nothing fundamentally different in what we're doing.

To put it another way, leap spooky adds two extra actions at the end of the command. In my mind, those actions are orchestrated by the operator, not the text object. Text objects are just regions, arguments passed to an operator, providing a start and an end position.

I'd also argue that having new operators makes leap-spooky more extensible. If I define some new text objects through treesitter-text-objects, or mini.ai, leap-spooky can also act on those same text objects, without having to define its own 'special' ones.

Also keep in mind that Spooky wouldn't work in Visual mode, if it would define its own special operators, instead of text objects. (By definition.)

I think this makes sense. In visual mode the built in operators execute immediately on what you currently have selected, you don't provide a text object to them. Invoking the leap-spooky operators should either go back to normal mode and then behave normally, or perform the non-remote equivalents. (e.g. yr just performs a y on your current selection). I appreciate this take might be opinionated but I am just putting forth what I think would make sense and stay within the vim-text-objects 'model' as I see it.

ggandor commented 1 year ago

In my mind, those actions are orchestrated by the operator, not the text object. Text objects are just regions, arguments passed to an operator, providing a start and an end position.

When someone says defining "text objects", obviously they just shorten "text object selection commands". iw is not a text object per se, what is passed to the operator is a command. Even if the command is arbitrarily complex, it has nothing to do with the operation itself, the result is still a selected region, that can be consumed by any operator.

I'd also argue that having new operators makes leap-spooky more extensible. If I define some new text objects through treesitter-text-objects, or mini.ai, leap-spooky can also act on those same text objects, without having to define its own 'special' ones.

Sure, but it's similar to the expression problem, either adding new operators or new text-objects will be awkward, choose one. (And as I mentioned, if spooky defines operators, we cannot virw... anymore. Which would be totally weird and inconsistent.)

Groz17 commented 1 year ago

I thought about this same idea today. I tried modifying leap-spooky.lua (this is my first time editing Lua code, so there may be errors) and this seems to work.

diff --git a/lua/leap-spooky.lua b/lua/leap-spooky.lua
index 2c3f021..f19ad14 100644
--- a/lua/leap-spooky.lua
+++ b/lua/leap-spooky.lua
@@ -80,2 +80,3 @@ local function setup(kwargs)
   local kwargs = kwargs or {}
+  local op = kwargs.op
   local affixes = kwargs.affixes
@@ -102,3 +103,3 @@ local function setup(kwargs)
           keeppos = keeppos,
-          lhs = textobj:sub(1,1) .. key .. textobj:sub(2),
+          lhs = (op and (key .. textobj:sub(1,1) .. textobj:sub(2))) or (textobj:sub(1,1) .. key .. textobj:sub(2)),
           action = function ()

You also need to add op = true in leap-spooky's setup function (change op to false to restore the original behavior).

I prefer these mappings for the following reasons:

Another improvement I'd like to see is being able to target (a subset of) text objects (even those given by treesitter) with only one letter, the same way you type ciw or cw to change a word in Vim.

Thankfully, in leap-spooky's case the current cursor position has no importance since you target text objects by jumping, so typing for example drw to delete a remote word or cmf to change a remote function should have the same effect as typing driw or cmif.

chrisgrieser commented 1 year ago

I also had the same thought, that yraw is more intuitive than yarw.

I could make it work by adding a bunch of remappings though:

local spooky = "x" -- key used in the affix-config
local textobjRemaps = { "b", "B", '"', "'", "`", "[", "w", "W", "(", "{" }
for _, key in pairs(textobjRemaps) do
    vim.keymap.set(
        "o",
        spooky .. "a" .. key,
        "a" .. spooky .. key,
        { desc = "Distant outer " .. key, remap = true }
    )
    vim.keymap.set(
        "o",
        spooky .. "i" .. key,
        "i" .. spooky .. key,
        { desc = "Distant inner " .. key, remap = true }
    )
end
ggandor commented 1 year ago

Hopefully resolved by https://github.com/ggandor/leap-spooky.nvim/commit/73c8fe21776275b54e875a89aae240d60b8b6af1. (Note that r will not be a good choice anymore if one chooses the prefix option, at least it conflicts with the native "replace" in Visual mode.)

al-ce commented 1 year ago

Thanks @ggandor and @Groz17, very nice

ggandor commented 1 year ago

Just a heads up, sorry https://github.com/ggandor/leap-spooky.nvim/commit/808bd887388a58ea9ddec6a420a414416e5d3655