local esc = vim.api.nvim_replace_termcodes("", true, false, true) local ctrl_c = vim.api.nvim_replace_termcodes("", true, false, true) local ctrl_v = vim.api.nvim_replace_termcodes("", true, false, true) local set_curpos = function(pos) vim.api.nvim_win_set_cursor(0, { pos[1], pos[2] - 1 }) end local set_lines = function(lines) vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) end local check_lines = function(lines) assert.are.same(lines, vim.api.nvim_buf_get_lines(0, 0, -1, false)) end describe("nvim-surround", function() before_each(function() local bufnr = vim.api.nvim_create_buf(true, true) vim.api.nvim_win_set_buf(0, bufnr) end) it("can surround text-objects (ys, yss)", function() set_lines({ "local str = test" }) set_curpos({ 1, 13 }) vim.cmd('normal ysiw"') check_lines({ 'local str = "test"' }) vim.cmd('normal ysa"b') check_lines({ 'local str = ("test")' }) set_lines({ "string x = some text" }) set_curpos({ 1, 12 }) vim.cmd('normal ys$"') check_lines({ 'string x = "some text"' }) set_lines({ "this is a", "part of a paragraph", "with multiple lines", }) set_curpos({ 2, 8 }) vim.cmd("normal ysipB") check_lines({ "{this is a", "part of a paragraph", "with multiple lines}", }) set_lines({ "hello world" }) vim.cmd("normal yssb") check_lines({ "(hello world)" }) set_curpos({ 1, 5 }) set_lines({ "this is the first line", "followed by the second line", "and finally the third" }) vim.cmd("normal 2yssB") set_lines({ "{this is the first line", "followed by the second line}", "and finally the third" }) end) it("can surround text-objects (yS, ySS)", function() set_lines({ "local str = test" }) set_curpos({ 1, 13 }) vim.cmd("normal yS2br") check_lines({ "local [", "str =", "] test" }) set_curpos({ 1, 1 }) vim.cmd("normal ySa]B") check_lines({ "local {", "[", "str =", "]", "} test" }) set_lines({ "this is a", "part of a paragraph", "with multiple lines", }) set_curpos({ 2, 8 }) vim.cmd("normal ySipB") check_lines({ "{", "this is a", "part of a paragraph", "with multiple lines", "}", }) set_lines({ "current line" }) set_curpos({ 1, 7 }) vim.cmd("normal ySSr") check_lines({ "[", "current line", "]" }) set_lines({ "here", "we", "have", "several", "lines" }) set_curpos({ 2, 2 }) vim.cmd("normal 3ySS`") check_lines({ "here", "`", "we", "have", "several", "`", "lines" }) end) it("can surround empty lines", function() set_lines({ "" }) vim.cmd("normal ysla") check_lines({ "<>" }) set_lines({ "" }) set_curpos({ 1, 1 }) vim.cmd("normal ySla") check_lines({ "<", "", ">" }) set_lines({ "" }) set_curpos({ 1, 1 }) vim.cmd("normal vSb") check_lines({ "()" }) set_lines({ "" }) set_curpos({ 1, 1 }) vim.cmd("normal vgSB") check_lines({ "{", "", "}" }) set_lines({ "" }) set_curpos({ 1, 1 }) vim.cmd("normal VSr") check_lines({ "[", "", "]" }) set_lines({ "" }) set_curpos({ 1, 1 }) vim.cmd("normal " .. ctrl_v .. "Sb") check_lines({ "()" }) end) it("properly handles whitespace for open/close pairs", function() set_lines({ "sample_text" }) vim.cmd("normal ysiw}") check_lines({ "{sample_text}" }) set_lines({ "sample_text" }) vim.cmd("normal ysiw{") check_lines({ "{ sample_text }" }) set_lines({ "({ sample_text })" }) vim.cmd("normal ds{") check_lines({ "(sample_text)" }) set_lines({ "({sample_text})" }) vim.cmd("normal ds{") check_lines({ "(sample_text)" }) set_lines({ "({sample_text })" }) vim.cmd("normal ds{") check_lines({ "(sample_text)" }) set_lines({ "({ sample_text })" }) vim.cmd("normal ds}") check_lines({ "( sample_text )" }) vim.cmd("normal cs()") check_lines({ "(sample_text)" }) end) it("properly handles whitespace next to line boundaries", function() set_lines({ "sample_text" }) vim.cmd("normal ySS{") check_lines({ "{", "sample_text", "}" }) vim.cmd("normal cs}[") check_lines({ "[", "sample_text", "]" }) set_lines({ "(", " sample_text", ")" }) vim.cmd("normal cs)<") check_lines({ "<", " sample_text", ">" }) set_lines({ " ", }) vim.cmd("normal cs><") check_lines({ " < texta", " textb", " >", }) vim.cmd("normal cs>{") check_lines({ " { texta", " textb", " }", }) vim.cmd("normal cs{]") check_lines({ " [ texta", " textb", " ]", }) vim.cmd("normal cs[)") check_lines({ " (texta", " textb", " )", }) set_lines({ " {texta", " textb", " textc}", }) vim.cmd("normal cs{{") check_lines({ " { texta", " textb", " textc }", }) vim.cmd("normal cs{{") check_lines({ " { texta", " textb", " textc }", }) vim.cmd("normal cs}{") check_lines({ " { texta", " textb", " textc }", }) vim.cmd("normal cs{}") check_lines({ " { texta", " textb", " textc }", }) vim.cmd("normal cs{}") check_lines({ " {texta", " textb", " textc}", }) end) it("can surround charwise visual selections", function() set_lines({ "this is", "a full", "sentence" }) set_curpos({ 1, 2 }) vim.cmd("normal! v") set_curpos({ 3, 5 }) vim.cmd("normal SB") check_lines({ "t{his is", "a full", "sente}nce" }) set_curpos({ 3, 7 }) vim.cmd("normal! v") set_curpos({ 2, 2 }) vim.cmd("normal Sa") check_lines({ "t{his is", "a< full", "sente}n>ce" }) set_lines({ "this is", "a full", "sentence" }) set_curpos({ 1, 1 }) vim.cmd("normal! v") set_curpos({ 3, 8 }) vim.cmd("normal gS'") check_lines({ "'", "this is", "a full", "sentence", "'" }) end) it("can surround linewise visual selections", function() set_lines({ "current line" }) vim.cmd("normal VSB") check_lines({ "{", "current line", "}" }) set_lines({ "multiple", "lines" }) set_curpos({ 2, 2 }) vim.cmd("normal! V") set_curpos({ 1, 3 }) vim.cmd("normal Sb") check_lines({ "(", "multiple", "lines", ")" }) set_curpos({ 1, 1 }) set_lines({ "'hello',", "'hello',", "'hello',", }) vim.cmd("normal VSB") check_lines({ "{", "'hello',", "}", "'hello',", "'hello',", }) set_curpos({ 5, 1 }) vim.cmd("normal VSB") check_lines({ "{", "'hello',", "}", "'hello',", "{", "'hello',", "}", }) vim.cmd("normal ggVGSb") check_lines({ "(", "{", "'hello',", "}", "'hello',", "{", "'hello',", "}", ")", }) end) it("can surround blockwise visual selections", function() set_lines({ "there happen", "to be", "quite a few lines", "in this buffer", "or so I had thought", }) vim.cmd("normal! " .. ctrl_v) set_curpos({ 5, 2 }) vim.cmd("normal Sb") check_lines({ "(th)ere happen", "(to) be", "(qu)ite a few lines", "(in) this buffer", "(or) so I had thought" }) set_curpos({ 5, 21 }) vim.cmd("normal! " .. ctrl_v) set_curpos({ 1, 1 }) vim.cmd("normal SB") check_lines({ "{(th)ere happen}", "{(to) be}", "{(qu)ite a few lines}", "{(in) this buffer}", "{(or) so I had thought}", }) set_curpos({ 4, 3 }) vim.cmd("normal! " .. ctrl_v) set_curpos({ 3, 7 }) vim.cmd("normal Sa") check_lines({ "{(th)ere happen}", "{(to) be}", "{(e a few lines}", "{(his buffer}", "{(or) so I had thought}", }) set_curpos({ 1, 16 }) vim.cmd("normal! " .. ctrl_v) set_curpos({ 3, 16 }) vim.cmd("normal S'") check_lines({ "{(th)ere happen'}'", "{(to) be}''", "{(e a fe'w' lines}", "{(his buffer}", "{(or) so I had thought}", }) set_lines({ "hello world", "more text here" }) set_curpos({ 1, 3 }) vim.cmd("normal! " .. ctrl_v) set_curpos({ 2, 10 }) vim.cmd("normal gSB") check_lines({ "he{", "llo worl", "}d", "mo{", "re text ", "}here", }) end) it("can delete delimiter pairs", function() set_lines({ "local func = function()", " local config = require('nvim-surround.config')", " local tab = {", " `weird stuff`,", " },", "end", }) vim.cmd("normal dsb") check_lines({ "local func = function", " local config = require('nvim-surround.config')", " local tab = {", " `weird stuff`,", " },", "end", }) vim.cmd("normal dsf") check_lines({ "local func = function", " local config = 'nvim-surround.config'", " local tab = {", " `weird stuff`,", " },", "end", }) vim.cmd("normal dsB") check_lines({ "local func = function", " local config = 'nvim-surround.config'", " local tab = ", " `weird stuff`,", " ,", "end", }) set_lines({ "()" }) vim.cmd("normal dsb") check_lines({ "" }) set_lines({ "{", "", "}" }) vim.cmd("normal dsB") check_lines({ "", "", "" }) end) it("can change delimiter pairs", function() set_lines({ "require'nvim-surround'.setup(", "", ")", }) set_curpos({ 1, 8 }) vim.cmd("normal ysa'b") set_curpos({ 1, 9 }) vim.cmd([[normal cs'"]]) set_curpos({ 2, 1 }) vim.cmd("normal csbB") vim.cmd("normal ysaBb") check_lines({ 'require("nvim-surround").setup({', "", "})", }) set_lines({ "()" }) vim.cmd("normal csba") check_lines({ "<>" }) set_lines({ "{", "", "}" }) vim.cmd("normal csBr") check_lines({ "[", "", "]" }) end) it("can change delimiter pairs on new lines", function() set_lines({ "require'nvim-surround'.setup(args)", }) set_curpos({ 1, 29 }) vim.cmd("normal cS))") check_lines({ "require'nvim-surround'.setup(", "args", ")", }) set_curpos({ 2, 1 }) vim.cmd([[normal ysab}]]) set_curpos({ 1, 29 }) vim.cmd("normal cS}]") check_lines({ "require'nvim-surround'.setup[", "(", "args", ")", "]", }) vim.cmd("normal cS]{") check_lines({ "require'nvim-surround'.setup{", "", "(", "args", ")", "", "}", }) set_lines({ "()" }) vim.cmd("normal cSbb") check_lines({ "(", "", ")" }) set_lines({ "{", "", "}" }) vim.cmd("normal cSBB") check_lines({ "{", "", "", "", "}" }) end) it("can handle invalid key behavior", function() set_lines({ "sample text" }) vim.cmd("normal yss|") check_lines({ "|sample text|" }) vim.cmd("normal cs|^") check_lines({ "^sample text^" }) vim.cmd("normal ds^") check_lines({ "sample text" }) set_lines({ "one|two|three|four|five" }) set_curpos({ 1, 2 }) vim.cmd("normal ds|") check_lines({ "onetwothree|four|five" }) set_lines({ "one|two|three|four|five" }) set_curpos({ 1, 7 }) vim.cmd("normal ds|") check_lines({ "onetwothree|four|five" }) set_lines({ "one|two|three|four|five" }) set_curpos({ 1, 8 }) vim.cmd("normal ds|") check_lines({ "one|twothreefour|five" }) set_lines({ "one|two|three|four|five" }) set_curpos({ 1, 15 }) vim.cmd("normal ds|") check_lines({ "one|two|threefourfive" }) set_lines({ "one|two|three|four|five" }) set_curpos({ 1, 23 }) vim.cmd("normal ds|") check_lines({ "one|two|threefourfive" }) set_lines({ "some |text|", "more |text|" }) set_curpos({ 2, 2 }) vim.cmd("normal ds|") check_lines({ "some |text", "more text|", }) end) it("can handle multi-byte characters", function() set_lines({ "。。。。" }) vim.cmd("normal ys3lb") check_lines({ "(。。。)。" }) vim.cmd("normal ysibB") check_lines({ "({。。。})。" }) set_curpos({ 1, 3 }) vim.cmd("normal yslr") check_lines({ "({[。]。。})。" }) vim.cmd("normal VS|") check_lines({ "|", "({[。]。。})。", "|" }) set_lines({ "1234567890", "。。。。。", "1234567890", }) set_curpos({ 2, 4 }) vim.cmd("normal! v") set_curpos({ 3, 7 }) vim.cmd("normal Sb") check_lines({ "1234567890", "。(。。。。", "1234567)890", }) set_curpos({ 2, 16 }) vim.cmd("normal! v") set_curpos({ 2, 11 }) vim.cmd("normal Sr") check_lines({ "1234567890", "。(。。[。。]", "1234567)890", }) set_lines({ "1234567890", "。。。。。", "1234567890", }) set_curpos({ 1, 10 }) vim.cmd("normal! " .. ctrl_v) set_curpos({ 3, 10 }) vim.cmd("normal Sb") check_lines({ "123456789(0)", "。。。。(。)", "123456789(0)", }) set_curpos({ 1, 7 }) vim.cmd("normal! " .. ctrl_v) set_curpos({ 3, 4 }) vim.cmd("normal Sr") check_lines({ "123[4567]89(0)", "。[。。。](。)", "123[4567]89(0)", }) set_curpos({ 3, 1 }) vim.cmd("normal! " .. ctrl_v) set_curpos({ 1, 1 }) vim.cmd("normal Sa") check_lines({ "<1>23[4567]89(0)", "<。>[。。。](。)", "<1>23[4567]89(0)", }) set_lines({ "我现在在写中文字。", "我不太知道应该些什么。", "这样也可以练中文哈哈。", }) set_curpos({ 1, 1 }) vim.cmd("normal! 3l" .. ctrl_v .. "2j2h") vim.cmd("normal Sa") check_lines({ "我<现在在>写中文字。", "我<不太知>道应该些什么。", "这<样也可>以练中文哈哈。", }) set_lines({ "𐍈𐍈𐍈𐍈𐍈𐍈𐍈𐍈𐍈𐍈", "。。。。。", "𐍈𐍈𐍈𐍈𐍈𐍈𐍈𐍈𐍈𐍈", }) set_curpos({ 1, 25 }) vim.cmd("normal! " .. ctrl_v) set_curpos({ 3, 13 }) vim.cmd("normal Sb") check_lines({ "𐍈𐍈𐍈(𐍈𐍈𐍈𐍈)𐍈𐍈𐍈", "。(。。。)。", "𐍈𐍈𐍈(𐍈𐍈𐍈𐍈)𐍈𐍈𐍈", }) require("nvim-surround").setup({ surrounds = { ["x"] = { add = { "‘", "’" }, find = "‘[^‘’]*’", }, ["y"] = { add = { "‘‘", "’’" }, find = "‘‘[^‘’]*’’", delete = "^(‘‘)().-(’’)()$", }, }, }) set_lines({ "‘foo bar’", }) set_curpos({ 1, 5 }) vim.cmd("normal csx_") check_lines({ "_foo bar_", }) set_lines({ "‘‘foo bar baz’’", }) set_curpos({ 1, 3 }) vim.cmd("normal dsy") check_lines({ "foo bar baz", }) end) it("can properly use line-wise surrounds", function() vim.bo.filetype = "lua" vim.bo.shiftwidth = 4 vim.bo.expandtab = true set_lines({ "local f = function()", " local a = 123", "end", }) set_curpos({ 2, 2 }) vim.cmd('normal VS"') check_lines({ "local f = function()", ' "', " local a = 123", ' "', "end", }) vim.bo.filetype = nil vim.bo.shiftwidth = 8 vim.bo.expandtab = false end) it("can surround linewise normal mode selections", function() set_lines({ "I have this text file, and my cursor is here | let's say", "then I want to surround these two lines with parenthesis", }) set_curpos({ 1, 46 }) vim.cmd("normal ysjb") check_lines({ "(I have this text file, and my cursor is here | let's say", "then I want to surround these two lines with parenthesis)", }) set_lines({ "some more arbitrary", "text that's spread across", "multiple lines", "or something", "i guess", }) set_curpos({ 2, 6 }) vim.cmd("normal ys2jB") check_lines({ "some more arbitrary", "{text that's spread across", "multiple lines", "or something}", "i guess", }) end) it("can cancel adding delimiters to the buffer", function() set_lines({ "Hello, world!", }) set_curpos({ 1, 8 }) vim.cmd("normal ysiw" .. esc) vim.cmd("normal ysiw" .. ctrl_c) check_lines({ "Hello, world!", }) end) it("can cancel deleting delimiters from the buffer", function() set_lines({ "Hello, (world)!", }) vim.cmd("normal ds" .. esc) vim.cmd("normal ds" .. ctrl_c) check_lines({ "Hello, (world)!", }) end) it("can cancel changing delimiters in the buffer", function() set_lines({ "Hello, (world)!", }) vim.cmd("normal cs" .. esc) vim.cmd("normal csb" .. esc) vim.cmd("normal cs" .. ctrl_c) vim.cmd("normal cs" .. ctrl_c) check_lines({ "Hello, (world)!", }) end) it("doesn't re-indent visual surrounds for one line", function() set_lines({ "data Foo = Foo", " { fooA :: !Decimal", " , fooB :: !Decimal", " }", }) vim.opt.cindent = true set_curpos({ 3, 14 }) vim.cmd("normal viwS)") check_lines({ "data Foo = Foo", " { fooA :: !Decimal", " , fooB :: !(Decimal)", " }", }) end) end)