Skip to content

Latest commit

 

History

History
129 lines (108 loc) · 4.37 KB

File metadata and controls

129 lines (108 loc) · 4.37 KB

Clasp.nvim ⎇

Clever Adaptive Surround Pairs

A pair wrapping plugin with:

  • Single Keymap (<c-l> to cycling forward/backward, u to undo previous cycle)
  • Incremental node traversal (TS-powered)
  • Multi-cursor aware, works with multicursor.nvim

Show case with multicursor.nvim

iShot_2025-02-23_16.38.45.mp4

Installation

With lazy.nvim:

return {
    "xzbdmw/clasp.nvim",
    config = function()
        require("clasp").setup({
            pairs = { ["{"] = "}", ['"'] = '"', ["'"] = "'", ["("] = ")", ["["] = "]", ["<"] = ">" },
            -- If called from insert mode, do not return to normal mode.
            keep_insert_mode = true,
            -- consider the following go code:
            --
            -- `var s make()[]int`
            --
            -- if we want to turn it into:
            --
            -- `var s make([]int)`
            --
            -- Directly parse would produce wrong nodes, so clasp always removes the
            -- entire pair (`()` in this case) before parsing, in this case what the
            -- parser would see is `var s make[]int`, but this is still not valid
            -- grammar. For a better parse tree, we can aggressively remove all
            -- alphabetic chars before cursor, so it becomes:
            --
            -- `var s []int`
            --
            -- Now we can correctly wrap the entire `[]int`, because it is identified
            -- as a node. By default we only remove current pair(s) before parsing, in
            -- most cases this is fine, but you can set `remove_pattern = "[a-zA-Z_%-]+$"`
            -- to use a more aggressive approach if you run into problems.
            remove_pattern = nil,
        })

        -- jumping from smallest region to largest region.
        -- Press 'u' in normal mode or bind '<cmd>undo<cr>'
        -- in insert mode to undo. You can interleave undo and wrap.

        -- Initial state:
        -- func(|)vim.keymap.foo('bar')

        -- Keep pressing <c-l>:
        -- func(vim)|.keymap.foo('bar')
        -- func(vim.keymap)|.foo('bar')
        -- func(vim.keymap.foo('bar'))|

        -- Press 'u'
        -- func(vim.keymap)|.foo('bar')
        vim.keymap.set({ "n", "i" }, "<c-l>", function()
            require("clasp").wrap('next')
        end)

        -- jumping from largest region to smallest region

        -- Initial state:
        -- func(|)vim.keymap.foo('bar')

        -- Keep pressing <c-;>
        -- func(vim.keymap.foo('bar'))|
        -- func(vim.keymap)|.foo('bar')
        -- func(vim)|.keymap.foo('bar')

        -- Press 'u'
        -- func(vim.keymap)|.foo('bar')
        vim.keymap.set({ "n", "i" }, "<c-;>", function()
            require("clasp").wrap('prev')
        end)

        -- If you want to exclude nodes whose end row is not current row
        vim.keymap.set({ "n", "i" }, "<c-l>", function()
            require("clasp").wrap('next', function(nodes)
                local n = {}
                for _, node in ipairs(nodes) do
                    if node.end_row == vim.api.nvim_win_get_cursor(0)[1] - 1 then
                        table.insert(n, node)
                    end
                end
                return n
            end)
        end)
    end,
}

Limitations

If you have multiple cursors, make sure you call wrap in normal mode. It is suggested to define mappings like this, so you can keep pressing <c-l> the same in insert mode:

vim.keymap.set({ "n", "i" }, "<c-l>", function()
    if
        vim.fn.mode() == "i"
        and package.loaded["multicursor-nvim"]
        and require("multicursor-nvim").numCursors() > 1
    then
        vim.cmd("stopinsert")
    else
        require("clasp").wrap("next")
    end
end)

Similar plugins

  • ultimate-autopair.nvim
    • Uses a regex heuristic combined with already matched pairs to determine right pair position, while this plugin relies on treesitter.
    • The main motivation for writting this plugin is to support multi-cursor. So it works on normal mode and remembers each cursor's parsing state.
  • nvim-autopairs
    • Uses hint based fast wrap, each possible position has one label, only works for current line.

License

MIT License.