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

Pass 'text to be surrounded' into custom callback function used to determine left and right surroundings #29

Closed andrewferrier closed 2 years ago

andrewferrier commented 2 years ago

As per the idea here: https://github.com/kylechui/nvim-surround/issues/27#issuecomment-1178793314.

andrewferrier commented 2 years ago

Agreed I can't think of a good usecase right now either (!), but it seems very interesting in principle and could open up some creativity. Maybe some cleverness with treesitter, if the object surrounded is known to be of a particular type, that could affect the surrounding.

I think this issue is definitely less essential though, from my personal perspective at least.

kylechui commented 2 years ago

@andrewferrier I've implemented it in the latest commit. The argument that's passed into the function is going to be an array of strings, where each string represents a line. For example, if | demarcates the selection, then

ab|cde
fghi|j

would be represented as { "cde", "fghi" }. I tested it out with a basic buffer-local option:

-- ftplugin/cpp.lua
require("nvim-surround").buffer_setup({
    delimiters = {
        pairs = {
            ["c"] = function(text)
                -- If the surrounded text is one line consisting of all digits
                if #text == 1 and text[1]:match("^%d+$") then
                    return { "to_string(", ")" }
                end
                return { "stoi(", ")" }
            end,
        }
    }
})

Surrounding numbers using c will wrap the number in to_string(text), while anything else will be wrapped in stoi(text).

kylechui commented 2 years ago

I've updated the README video with some of the new features, let me know if there's anything you feel I'm missing that should be emphasized more

andrewferrier commented 2 years ago

Nice! That change looks really good. Might be good to show your stoi() example in the README to show people one way that feature could be used. Otherwise looks pretty good right now!

andrewferrier commented 2 years ago

@kylechui I might be polishing the nosecone here a little, but in terms of providing a complete "API" for someone implementing this custom function, I wonder if it would be worth providing the start and end positions (row and col) too, as well as the buffer number? That enables the person implementing the pair to use other vim functions to look stuff up, check for treesitter objects, etc. if they wanted.

kylechui commented 2 years ago

@andrewferrier This has been amended in the latest commit. The new API is that a table args is now passed to the function, which has the following keys:

I documented this a little bit in :h nvim-surround

Edit: Merging this into main, as using an args table should mean that the API is stable, and all of the current tests are passing.

andrewferrier commented 2 years ago

Nice! I'm still struggling to think of some good use cases right now but I'm sure there will be some... this is a really nice generalized capability that hopefully folks can use creatively.

kylechui commented 2 years ago

@andrewferrier This doesn't use the args table, but I think is pretty cool for C++ files. It basically just adds std:: if you aren't using the standard namespace for printing:

["p"] = function()
    for _, line in ipairs(vim.api.nvim_buf_get_lines(0, 0, -1, false)) do
        print(line)
        if line == "using namespace std;" then
            return { "cout << ", " << endl;" }
        end
    end
    return { "std::cout << ", " << std::endl;" }
end,

That way you can quickly surround a line in a print statement with yssp. I still don't know enough about TreeSitter nor LSP to meaningfully "hook into them", but yeah hopefully some smarter people can come along and figure that out haha.

Edit: Honestly at this point this is almost reminding me of a snippet engine, perhaps we can borrow a few ideas from there... :eyes:

andrewferrier commented 2 years ago

Nice! I don't write C++ but I understand the basic principle. Great plugability... worth highlighting this when you advertise the plugin, I would suggest.

kylechui commented 2 years ago

@andrewferrier Hi again! I am now one day older, and one day wiser, and have learned how to use TreeSitter (somewhat). Here's an example of using different surrounds for different fenced code blocks in a markdown file:

https://user-images.githubusercontent.com/48545987/178377898-dde3c074-2f8c-4360-b121-d1dabd0b2085.mp4

However, this still doesn't actually make use of the args table that this feature provides; I might consider removing it after a while if there aren't any applications of it. If you're curious, here's the corresponding configuration code (it's a bit much):

Code implementation ```lua local surr_utils = require("nvim-surround.utils") local ts_utils = require("nvim-treesitter.ts_utils") local query = vim.treesitter.query require("nvim-surround").buffer_setup({ delimiters = { pairs = { ["f"] = function() local cur = ts_utils.get_node_at_cursor(0, true) while cur and cur:type() ~= "fenced_code_block" do cur = cur:parent() end local language = nil if cur then for child_node in cur:iter_children() do if child_node:type() == "info_string" then language = query.get_node_text(child_node:child(0), 0) end end end if language == "cpp" then local input = surr_utils.get_input("Enter a function name: ") if input then return { "auto " .. input .. " = [](", { "){", " return 0;", "}", } } end elseif language == "lua" then local input = surr_utils.get_input("Enter a function name: ") if input then return { "local " .. input .. " = function(", { ")", " return nil", "end", } } end elseif language == "python" then local input = surr_utils.get_input("Enter a function name: ") if input then return { input .. " = lambda ", { ":", " return NULL" } } end end return { "", "" } end, }, }, }) ```
andrewferrier commented 2 years ago

Yeah, that's really interesting! It's get_node_at_cursor() that really unlocks the magic here, I guess. What I like about this is that even though the surrounds are still fairly simple, you are able to make them language dependent.

Understand about the args table. Personally I think it probably still allows for some magic, but I'm not the one who has to maintain it. I would still leave it a while and see if anyone does anything with it.

It might be worth another post on reddit etc. highlighting some of the "customizability" you've added to get folks' creative juices flowing...

kylechui commented 2 years ago

Yep, I was planning on making another post soon-ish to really show off all of the new features/configuration that I've added since 2 weeks ago, I'll also maybe open a discussion post about it to see if people have any ideas there.

kylechui commented 2 years ago

@andrewferrier Probably going to remove this feature with the advent of pattern-based surrounds since to my knowledge nobody has found any use for it whatsoever, and the extra bloat was annoying me haha (commit).

andrewferrier commented 2 years ago

All good :)