kylechui / nvim-surround

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

Allow updating tags with punctuation #127

Closed emi2k01 closed 2 years ago

emi2k01 commented 2 years ago

Checklist

To reproduce

Try to replace a tag with <foo.bar>, it will only replace it with <foo>

Expected behavior

Tags are updated with the whole input

Actual behavior

Tags are truncated up to the first punctuation character

Additional context

This is useful for react components

kylechui commented 2 years ago

Hi there, thanks for reporting a bug! I don't actually know what characters are "valid" for HTML; is it just alphanums + hyphens + periods?

kylechui commented 2 years ago

Maybe I'll just make it "any non-whitespace character"; do you mind testing out the following config? Sorry for the verboseness:

local config = require("nvim-surround.config")
require("nvim-surround").setup({
    surrounds = {
        ["t"] = {
            add = function()
                local input = M.get_input("Enter the HTML tag: ")
                if input then
                    local element = input:match("^<?([^%s>]*)")
                    local attributes = input:match("^<?[^%s>]*%s+(.-)>?$")

                    local open = attributes and element .. " " .. attributes or element
                    local close = element

                    return { { "<" .. open .. ">" }, { "</" .. close .. ">" } }
                end
            end,
            find = function()
                return M.get_selection({ textobject = "t" })
            end,
            delete = "^(%b<>)().-(%b<>)()$",
            change = {
                target = "^<([^%s>]*)().-([^/]*)()>$",
                replacement = function()
                    local input = M.get_input("Enter the HTML tag: ")
                    if input then
                        local element = input:match("^<?([^%s>]*)")
                        local attributes = input:match("^<?[^%s>]*%s+(.-)>?$")

                        local open = attributes and element .. " " .. attributes or element
                        local close = element

                        return { { open }, { close } }
                    end
                end,
            },
        },
        ["T"] = {
            add = function()
                local input = M.get_input("Enter the HTML tag: ")
                if input then
                    local element = input:match("^<?([^%s>]*)")
                    local attributes = input:match("^<?[^%s>]*%s+(.-)>?$")

                    local open = attributes and element .. " " .. attributes or element
                    local close = element

                    return { { "<" .. open .. ">" }, { "</" .. close .. ">" } }
                end
            end,
            find = function()
                return M.get_selection({ textobject = "t" })
            end,
            delete = "^(%b<>)().-(%b<>)()$",
            change = {
                target = "^<([^>]*)().-([^/]*)()>$",
                replacement = function()
                    local input = M.get_input("Enter the HTML tag: ")
                    if input then
                        local element = input:match("^<?([^%s>]*)")
                        local attributes = input:match("^<?[^%s>]*%s+(.-)>?$")

                        local open = attributes and element .. " " .. attributes or element
                        local close = element

                        return { { open }, { close } }
                    end
                end,
            },
        },
    },
})
emi2k01 commented 2 years ago

For JSX, it's way too complex. It lets you do things like <Component<{ a: x, b: typeof (30 + 20) }> />.

If I read this correctly, I think anything goes: https://html.spec.whatwg.org/#tag-name-state

kylechui commented 2 years ago

Oh wow this is exactly what I was looking for; I'll try to write a Lua pattern for this soon-ish. @emi2k01 I don't think I'll be supporting self-closing tags since that kind of defeats the purpose of a "delimiter pair"; are there any examples of more complicated pairs of HTML/JSX tags? Also the link you sent seems to target HTML in particular, is there anything that describes JSX tags in the same way?

emi2k01 commented 2 years ago

Yes, there is: https://facebook.github.io/jsx/#prod-JSXElement

I think a simplified form of JSX would be good because it's too complex. Probably, doing it like HTML would be good. Basically, no generics because everything goes there (e.g. <Component<{ a: b, c: typeof (3+1)}>>bla</Component>)

So, the idea would be to match the tag name as any character before a space

emi2k01 commented 2 years ago

@kylechui I tried the config you commented earlier, it works good. Thanks!

kylechui commented 2 years ago

It's actually still scuffed, but if you know Lua patterns you can try fixing new problems as they arise. I've edited the original message with slightly better pattern-matching.

emi2k01 commented 2 years ago

I tried the last edit but for my use-case it's basically just replacing tags with hyphens and periods, and it works good.

Why do you say it's scuffed?

kylechui commented 2 years ago

The original one broke when you ended the tag with the optional >, since I forgot to account for that. I'm going to take a look at JSX syntax later and see if I can get something working there; if not then all non-whitespace seems like a "good enough" heuristic for me.

kylechui commented 2 years ago

For the most recent example, <Component<{ a: b, c: typeof (3+1)}>>bla</Component>, it's not even detected by Neovim's at text-object. I think the real answer here is to use "non-whitespace" as a heuristic for most HTML tags, and use Tree-sitter for anything more complicated (which is being worked on in #123).

kylechui commented 2 years ago

I installed the tsx Tree-sitter language parser, and it handles your complicated <Component<{ a: b, c: typeof (3+1)}>>bla</Component> case quite well. Funnily enough there's no jsx parser, although TBH I don't really code in either. Feel free to subscribe to #123 to stay up-to-date on how I handle integrating Tree-sitter into this plugin :)

emi2k01 commented 2 years ago

Awesome, thanks for the update!. Let me know if there's something I can help with

kylechui commented 2 years ago

I'll close this as "completed" for now (merging into #123); the two big things that you could do for me right now are

  1. Subscribe to #123 and weigh in on any user-interface design choices
  2. Later down the line, provide me with some "real" examples of "edge case" JSX tags, or just help me with testing in general and finding more bugs like this one

I appreciate you taking the time to report this!