echasnovski / mini.nvim

Library of 40+ independent Lua modules improving overall Neovim (version 0.8 and higher) experience with minimal effort
MIT License
4.45k stars 171 forks source link

count + next/last in nested surrounds #966

Closed simonmandlik closed 3 weeks ago

simonmandlik commented 3 weeks ago

Contributing guidelines

Module(s)

mini.surround

Description

I have noticed that whenever count is applied together with next/last extended mappings, it skips nested surrounds. But this is not the case when operations are executed individually.

I'm also having some trouble understanding the logic when both are applied

Neovim version

0.10

Steps to reproduce

minimal.lua:

local root = vim.fn.fnamemodify("./.repro", ":p")
for _, name in ipairs({ "config", "data", "state", "cache" }) do
  vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end
local lazypath = root .. "/plugins/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", lazypath, })
end
vim.opt.runtimepath:prepend(lazypath)

local plugins = {
    {
        "echasnovski/mini.surround",
        opts = {}
    },
}

require("lazy").setup(plugins, {
    root = root .. "/plugins",
})

Testing buffer:

<cursor>
(first, (second, (third)))
(fourth)

Expected behavior

2sdn(, 2shn(, 2sfn( all operate on (fourth), but for example sfn(sfn( or simply sfn(. ends up on (second (third)).

The role of count in this plugin is of course slightly different than usual in nvim, where it usually implies repetition of some operation. And in case of mini.surround the docs say "find n-th surrounding and execute operator on it". If this is the case, I would expect 2sdn( to first find "n(" and then delete the second outer pair (similarly to the 2sd) example in docs). The behavior is different however. Does this mean that extended mappings with count take precedence over the "standard" count behavior, which is explained in the docs?

Is there any clear explanation of this logic and motivation behind? I couldn't find any relevant info in docs, there are some examples with count, but no further explanation.

Thanks!

echasnovski commented 3 weeks ago

I have noticed that whenever count is applied together with next/last extended mappings, it skips nested surrounds. But this is not the case when operations are executed individually.

I'm also having some trouble understanding the logic when both are applied

Yeah, I agree that this behavior is not ideal, but it is somewhat consistent. The main logic is described in documentation of search algorithm. The crucial part here is "... repeated with current best match becoming reference region". With "next" search method (used in sdn, sfn, etc.) the match needs to have left/right edge on the right of left/right edge of reference region.

Testing buffer:

<cursor>
(first, (second, (third)))
(fourth)

So here first match is (first, (second, (third))) and it becomes reference region. If search with "next" is repeated, then the only match which satisfies the definition of "next" method is (fourth), as both (second, (third)) and (third) have right edge not to the right of reference region right edge.

The whole idea of this approach for search algorithm was that if the match is strictly inside current match, then it is not really "next" or "last". There maybe was other caveats I might have missed.

Hope it helps.