kylechui / nvim-surround

Add/change/delete surrounding delimiter pairs with ease. Written with :heart: in Lua.
MIT License
3.19k stars 62 forks source link

Allow for buffer-specific/filetype-specific mappings #7

Closed andrewferrier closed 2 years ago

andrewferrier commented 2 years ago

One thing that would be really nice is if nvim-surround supported filetype-specific or buffer-specific mappings. For example, vim-sandwich allows one to add local "recipes": https://github.com/machakann/vim-sandwich/blob/e114a5e0c90aefed3d2a48ca326eff9d39bc90a9/doc/sandwich.txt#L550.

I use this, for example, to add mappings for Markdown files that I don't use elsewhere. For me, it wouldn't be a huge problem to make them global for now, but this doesn't scale if folks want to have different mappings for the same key in different filetypes.

Perhaps something like

require("nvim-surround").add_buffer_local_delimiters({
        pairs = {
            ["L"] = { "[](", ")" }
        }
})

Presumably you'd store these in a buffer-local variable for the duration that the buffer exists.

... then folks can invoke that from after/ftplugin/markdown.lua or from inside an autocmd, or wherever they want...

pmrt commented 2 years ago

This would be great, not only local mappings per language but also entire configurations. For example, in some languages you may want to add a space between the delimiters and the object [ object ] as the default behavior while in other languages you don’t want that space when wrapping the object [object].

This is a silly example since in surround plugins is common to add spaces depending on whether you use an opening delimiter ‘[‘ or a closing one ‘]’. But it’s an example of how you could increase the flexibility of the plugin with this change without needing to code those specific features.

kylechui commented 2 years ago

Hmm I think this could be done via modifying the opts table in the config.lua file. I'll need to think about how I would make the changes persist for only the buffer though, as opposed to acting on all files.

akinsho commented 2 years ago

@kylechui surround does this using b: variables which are local to a buffer, you can do this in Lua with vim.b[bufnr].value = 1 or vim.api.nvim_buf_set_var(bufnr, "value", 1). There might be a bug with the first mechanism. It didn't use to work but might have since been fixed

kylechui commented 2 years ago

Thanks for the tip @akinsho, I just committed a simple version of buffer-local mappings to add-buffer-local-mappings. The setup interface is nearly identical, just use require("nvim-surround").buffer_setup({opts}) wherever you please. For example, one can overwrite the default f mapping by calling buffer_setup inside ftplugin files as follows:

-- ftplugin/python.lua
require("nvim-surround").buffer_setup({
    delimiters = {
        pairs = {
            ["f"] = function()
                return {
                    "def " .. require("nvim-surround.utils").get_input(
                        "Enter the function name: "
                    ) .. "(",
                    "):"
                }
            end,
        }
    }
})
-- ftplugin/lua.lua
require("nvim-surround").buffer_setup({
    delimiters = {
        pairs = {
            ["f"] = function()
                return {
                    "function " .. require("nvim-surround.utils").get_input(
                        "Enter the function name: "
                    ) .. "(",
                    ")"
                }
            end,
        }
    }
})

@andrewferrier and @pmrt also please test it out and let me know what you think! I still have some ugly code I need to clean up in the meantime though

akinsho commented 2 years ago

Tried this out, and it works pretty nicely @kylechui, nicely done. One thing which is frankly a separate issue, but I'm posting here since I noticed whilst refactoring from vim-surround, is that it allows using \r to enter new lines. For example, some vim buffer surrounds I have in my config that I was looking to replace are

let b:surround_{char2nr('i')} = "if \1if: \1 \r endif"
let b:surround_{char2nr('w')} = "while \1while: \1 \r endwhile"
let b:surround_{char2nr('f')} = "for \1for: \1 {\r endfor"
let b:surround_{char2nr('e')} = "foreach \1foreach: \1 \r enforeach"
let b:surround_{char2nr('F')} = "function! \1function: \1() \r endfunction"
let b:surround_{char2nr('T')} = "try \r endtry"

i.e. these will expand to be multiple lines. Again this isn't a blocker and not immediately related to this specific issue, I just noticed as I went to delete them then realized they wouldn't be on par (realistically I don't write much vimscript any more, so these are more likely to be deleted completely anyway).

kylechui commented 2 years ago

Hey @akinsho, I've just refactored it so that delimiters can now be tables instead of just strings, where each element of the table represents a different line to be inserted in the file. For example, to map i to wrap a condition inside

if condition then
    return nil
end

One could use the following config:

-- Not particularly useful but serves as a proof-of-concept
require("nvim-surround").buffer_setup({
    delimiters = {
        pairs = {
            ["i"] = function()
                return {
                    "if ",
                    {
                        " then",
                        "   return nil",
                        "end",
                    }
                }
            end
        }
    }
})

Also as a side-note @NoahTheDuke I might've accidentally resolved #20 in this commit, sorry

akinsho commented 2 years ago

Works really well ❤️, have officially fully moved over now since that was the final thing I really needed.

andrewferrier commented 2 years ago

Yes, this seems to work well - thanks! I have the simpler (not using a callback function) buffer-local mappings working well now.

kylechui commented 2 years ago

@andrewferrier Do you mean to say that there are still some issues/bugs with the branch? Or just that you have not yet tested it?

andrewferrier commented 2 years ago

Sorry for the lack of clarity. I mean that as far as I know it's working well. I've just used simpler pairs unlike @akinsho which don't use callback functions:

require("nvim-surround").buffer_setup({
    delimiters = {
        pairs = {
            ["l"] = { "[", "]()" },
            ["L"] = { "[](", ")" },
        },
    },
})

These work well though. From my perspective, you'd be good to merge and close this issue.

kylechui commented 2 years ago

Thanks everybody for all the feedback/testing, I really appreciate it!