From 5fa26e2c2fcc208ca31187de4338d5b6f746f2e1 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Thu, 29 Jul 2021 14:48:04 -0600 Subject: feat: add trimempty optional parameter to vim.split The `split()` VimL function trims empty items from the returned list by default, so that, e.g. split("\nhello\nworld\n\n", "\n") returns ["hello", "world"] The Lua implementation of vim.split does not do this. For example, vim.split("\nhello\nworld\n\n", "\n") returns {'', 'hello', 'world', '', ''} Add an optional parameter to the vim.split function that, when true, trims these empty elements from the front and back of the returned table. This is only possible for vim.split and not vim.gsplit; because vim.gsplit is an iterator, there is no way for it to know if the current item is the last non-empty item. Note that in order to preserve backward compatibility, the parameter for the Lua vim.split function is `trimempty`, while the VimL function uses `keepempty` (i.e. they are opposites). This means there is a disconnect between these two functions that may surprise users. --- runtime/doc/lua.txt | 15 +++++++++------ runtime/lua/vim/shared.lua | 32 ++++++++++++++++++++++++++++---- test/functional/lua/vim_spec.lua | 37 ++++++++++++++++++++++--------------- 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 53d68fa5e6..208a94e377 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1373,20 +1373,23 @@ pesc({s}) *vim.pesc()* See also: ~ https://github.com/rxi/lume -split({s}, {sep}, {plain}) *vim.split()* +split({s}, {sep}, {plain}, {trimempty}) *vim.split()* Splits a string at each instance of a separator. Examples: > split(":aa::b:", ":") --> {'','aa','','b',''} split("axaby", "ab?") --> {'','x','y'} - split(x*yz*o, "*", true) --> {'x','yz','o'} + split("x*yz*o", "*", true) --> {'x','yz','o'} + split("|x|y|z|", "|", true, true) --> {'x', 'y', 'z'} < Parameters: ~ - {s} String to split - {sep} Separator string or pattern - {plain} If `true` use `sep` literally (passed to - String.find) + {s} String to split + {sep} Separator string or pattern + {plain} If `true` use `sep` literally (passed to + String.find) + {trimempty} If `true` remove empty items from the front + and back of the list Return: ~ List-like table of the split components. diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 18c1e21049..e18dec9a45 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -98,17 +98,41 @@ end ---
 ---  split(":aa::b:", ":")     --> {'','aa','','b',''}
 ---  split("axaby", "ab?")     --> {'','x','y'}
----  split(x*yz*o, "*", true)  --> {'x','yz','o'}
+---  split("x*yz*o", "*", true)  --> {'x','yz','o'}
+---  split("|x|y|z|", "|", true, true) --> {'x', 'y', 'z'}
 --- 
--- +--- ---@see |vim.gsplit()| --- ---@param s String to split ---@param sep Separator string or pattern ---@param plain If `true` use `sep` literally (passed to String.find) +---@param trimempty If `true` remove empty items from the front and back of the list ---@returns List-like table of the split components. -function vim.split(s,sep,plain) - local t={} for c in vim.gsplit(s, sep, plain) do table.insert(t,c) end +function vim.split(s, sep, plain, trimempty) + -- Only need to validate trimempty since the rest are validated by vim.gsplit + vim.validate{trimempty={trimempty, 'b', true}} + local t = {} + local skip = trimempty + for c in vim.gsplit(s, sep, plain) do + if c ~= "" then + skip = false + end + + if not skip then + table.insert(t, c) + end + end + + if trimempty then + for i = #t, 1, -1 do + if t[i] ~= "" then + break + end + table.remove(t, i) + end + end + return t end diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index a066cfbc10..557923f648 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -237,27 +237,29 @@ describe('lua stdlib', function() end) it("vim.split", function() - local split = function(str, sep, plain) - return exec_lua('return vim.split(...)', str, sep, plain) + local split = function(str, sep, plain, trimempty) + return exec_lua('return vim.split(...)', str, sep, plain, trimempty) end local tests = { - { "a,b", ",", false, { 'a', 'b' } }, - { ":aa::bb:", ":", false, { '', 'aa', '', 'bb', '' } }, - { "::ee::ff:", ":", false, { '', '', 'ee', '', 'ff', '' } }, - { "ab", ".", false, { '', '', '' } }, - { "a1b2c", "[0-9]", false, { 'a', 'b', 'c' } }, - { "xy", "", false, { 'x', 'y' } }, - { "here be dragons", " ", false, { "here", "be", "dragons"} }, - { "axaby", "ab?", false, { '', 'x', 'y' } }, - { "f v2v v3v w2w ", "([vw])2%1", false, { 'f ', ' v3v ', ' ' } }, - { "", "", false, {} }, - { "", "a", false, { '' } }, - { "x*yz*oo*l", "*", true, { 'x', 'yz', 'oo', 'l' } }, + { "a,b", ",", false, false, { 'a', 'b' } }, + { ":aa::bb:", ":", false, false, { '', 'aa', '', 'bb', '' } }, + { ":aa::bb:", ":", false, true, { 'aa', '', 'bb' } }, + { "::ee::ff:", ":", false, false, { '', '', 'ee', '', 'ff', '' } }, + { "::ee::ff:", ":", false, true, { 'ee', '', 'ff' } }, + { "ab", ".", false, false, { '', '', '' } }, + { "a1b2c", "[0-9]", false, false, { 'a', 'b', 'c' } }, + { "xy", "", false, false, { 'x', 'y' } }, + { "here be dragons", " ", false, false, { "here", "be", "dragons"} }, + { "axaby", "ab?", false, false, { '', 'x', 'y' } }, + { "f v2v v3v w2w ", "([vw])2%1", false, false, { 'f ', ' v3v ', ' ' } }, + { "", "", false, false, {} }, + { "", "a", false, false, { '' } }, + { "x*yz*oo*l", "*", true, false, { 'x', 'yz', 'oo', 'l' } }, } for _, t in ipairs(tests) do - eq(t[4], split(t[1], t[2], t[3])) + eq(t[5], split(t[1], t[2], t[3], t[4])) end local loops = { @@ -288,6 +290,11 @@ describe('lua stdlib', function() vim/shared.lua:0: in function 'gsplit' vim/shared.lua:0: in function ]]), pcall_err(split, 'string', 'string', 1)) + eq(dedent([[ + Error executing lua: vim/shared.lua:0: trimempty: expected boolean, got number + stack traceback: + vim/shared.lua:0: in function ]]), + pcall_err(split, 'string', 'string', false, 42)) end) it('vim.trim', function() -- cgit From 84f66909e4008a57da947f1640bfc24da5e41a72 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sat, 25 Sep 2021 20:08:36 -0600 Subject: refactor: use kwargs parameter in vim.split --- runtime/doc/lua.txt | 24 +++++++++++++----------- runtime/lua/vim/shared.lua | 26 +++++++++++++++++++------- test/functional/lua/vim_spec.lua | 17 +++++++---------- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 208a94e377..4ceb123ffc 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1373,23 +1373,25 @@ pesc({s}) *vim.pesc()* See also: ~ https://github.com/rxi/lume -split({s}, {sep}, {plain}, {trimempty}) *vim.split()* +split({s}, {sep}, {kwargs}) *vim.split()* Splits a string at each instance of a separator. Examples: > - split(":aa::b:", ":") --> {'','aa','','b',''} - split("axaby", "ab?") --> {'','x','y'} - split("x*yz*o", "*", true) --> {'x','yz','o'} - split("|x|y|z|", "|", true, true) --> {'x', 'y', 'z'} + + split(":aa::b:", ":") --> {'','aa','','b',''} + split("axaby", "ab?") --> {'','x','y'} + split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'} + split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'} < Parameters: ~ - {s} String to split - {sep} Separator string or pattern - {plain} If `true` use `sep` literally (passed to - String.find) - {trimempty} If `true` remove empty items from the front - and back of the list + {s} String to split + {sep} Separator string or pattern + {kwargs} Keyword arguments: + • plain: (boolean) If `true` use `sep` literally + (passed to string.find) + • trimempty: (boolean) If `true` remove empty + items from the front and back of the list Return: ~ List-like table of the split components. diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index e18dec9a45..b57b7ad4ad 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -98,20 +98,32 @@ end ---
 ---  split(":aa::b:", ":")     --> {'','aa','','b',''}
 ---  split("axaby", "ab?")     --> {'','x','y'}
----  split("x*yz*o", "*", true)  --> {'x','yz','o'}
----  split("|x|y|z|", "|", true, true) --> {'x', 'y', 'z'}
+---  split("x*yz*o", "*", {plain=true})  --> {'x','yz','o'}
+---  split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'}
 --- 
--- ---@see |vim.gsplit()| --- ---@param s String to split ---@param sep Separator string or pattern ----@param plain If `true` use `sep` literally (passed to String.find) ----@param trimempty If `true` remove empty items from the front and back of the list +---@param kwargs Keyword arguments: +--- - plain: (boolean) If `true` use `sep` literally (passed to string.find) +--- - trimempty: (boolean) If `true` remove empty items from the front +--- and back of the list ---@returns List-like table of the split components. -function vim.split(s, sep, plain, trimempty) - -- Only need to validate trimempty since the rest are validated by vim.gsplit - vim.validate{trimempty={trimempty, 'b', true}} +function vim.split(s, sep, kwargs) + local plain + local trimempty = false + if type(kwargs) == 'boolean' then + -- Support old signature for backward compatibility + plain = kwargs + else + vim.validate { kwargs = {kwargs, 't', true} } + kwargs = kwargs or {} + plain = kwargs.plain + trimempty = kwargs.trimempty + end + local t = {} local skip = trimempty for c in vim.gsplit(s, sep, plain) do diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 557923f648..8651a38025 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -237,8 +237,8 @@ describe('lua stdlib', function() end) it("vim.split", function() - local split = function(str, sep, plain, trimempty) - return exec_lua('return vim.split(...)', str, sep, plain, trimempty) + local split = function(str, sep, kwargs) + return exec_lua('return vim.split(...)', str, sep, kwargs) end local tests = { @@ -259,9 +259,12 @@ describe('lua stdlib', function() } for _, t in ipairs(tests) do - eq(t[5], split(t[1], t[2], t[3], t[4])) + eq(t[5], split(t[1], t[2], {plain=t[3], trimempty=t[4]})) end + -- Test old signature + eq({'x', 'yz', 'oo', 'l'}, split("x*yz*oo*l", "*", true)) + local loops = { { "abc", ".-" }, } @@ -285,16 +288,10 @@ describe('lua stdlib', function() vim/shared.lua:0: in function ]]), pcall_err(split, 'string', 1)) eq(dedent([[ - Error executing lua: vim/shared.lua:0: plain: expected boolean, got number + Error executing lua: vim/shared.lua:0: kwargs: expected table, got number stack traceback: - vim/shared.lua:0: in function 'gsplit' vim/shared.lua:0: in function ]]), pcall_err(split, 'string', 'string', 1)) - eq(dedent([[ - Error executing lua: vim/shared.lua:0: trimempty: expected boolean, got number - stack traceback: - vim/shared.lua:0: in function ]]), - pcall_err(split, 'string', 'string', false, 42)) end) it('vim.trim', function() -- cgit