Skip to content

Commit 8f28402

Browse files
xudyang1L3MON4D3
authored andcommitted
feat(fmt): enable indent string conversion
1 parent 0f7bbce commit 8f28402

File tree

3 files changed

+182
-0
lines changed

3 files changed

+182
-0
lines changed

lua/luasnip/extras/fmt.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,15 @@ end
185185
-- opts: optional table
186186
-- trim_empty: boolean, remove whitespace-only first/last lines, default true
187187
-- dedent: boolean, remove all common indent in `str`, default true
188+
-- indent_string: string, convert `indent_string` at beginning of each line to unit indent ('\t')
189+
-- after applying `dedent`, default empty string (disabled)
188190
-- ... the rest is passed to `interpolate`
189191
-- Returns: list of snippet nodes
190192
local function format_nodes(str, nodes, opts)
191193
local defaults = {
192194
trim_empty = true,
193195
dedent = true,
196+
indent_string = "",
194197
}
195198
opts = vim.tbl_extend("force", defaults, opts or {})
196199

lua/luasnip/util/str.lua

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,46 @@ local function dedent(lines)
1818
end
1919
end
2020

21+
---Convert string `from` to unit indent
22+
---@param lines string[]
23+
---@param from string
24+
---@param unit_indent string
25+
local function convert_indent(lines, from, unit_indent)
26+
local from_length = #from
27+
if #lines == 0 or from_length == 0 or from == unit_indent then
28+
return
29+
end
30+
31+
local from_bytes = { string.byte(from, 1, from_length) }
32+
for i = 1, #lines do
33+
local line_bytes = { string.byte(lines[i], 1, #lines[i]) }
34+
local line_length = #line_bytes
35+
local indent_count = 0
36+
local j, k = 1, 1
37+
while j <= line_length and line_bytes[j] == from_bytes[k] do
38+
if k == from_length then
39+
indent_count = indent_count + 1
40+
end
41+
j = j + 1
42+
k = k % from_length + 1
43+
end
44+
if indent_count > 0 then
45+
lines[i] = string.format(
46+
"%s%s",
47+
string.rep(unit_indent, indent_count),
48+
string.sub(lines[i], from_length * indent_count + 1)
49+
)
50+
end
51+
end
52+
end
53+
2154
---Applies opts to lines.
2255
---lines is modified in-place.
2356
---@param lines string[].
2457
---@param options table, required, can have values:
2558
--- - trim_empty: removes empty first and last lines.
2659
--- - dedent: removes indent common to all lines.
60+
--- - indent_string: an unit indent at beginning of each line after applying `dedent`, default empty string (disabled)
2761
function M.process_multiline(lines, options)
2862
if options.trim_empty then
2963
if lines[1]:match("^%s*$") then
@@ -37,6 +71,10 @@ function M.process_multiline(lines, options)
3771
if options.dedent then
3872
dedent(lines)
3973
end
74+
75+
if options.indent_string and #options.indent_string > 0 then
76+
convert_indent(lines, options.indent_string, "\t")
77+
end
4078
end
4179

4280
function M.dedent(s)
@@ -45,6 +83,12 @@ function M.dedent(s)
4583
return table.concat(lst, "\n")
4684
end
4785

86+
function M.convert_indent(s, indent_string)
87+
local lst = vim.split(s, "\n")
88+
convert_indent(lst, indent_string, "\t")
89+
return table.concat(lst, "\n")
90+
end
91+
4892
local function is_escaped(s, indx)
4993
local count = 0
5094
for i = indx - 1, 1, -1 do

tests/unit/str_spec.lua

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,138 @@ describe("str.unescaped_pairs", function()
6060
{ { 1, 3 }, { 5, 8 }, { 9, 11 } }
6161
)
6262
end)
63+
64+
describe("str.dedent", function()
65+
-- apparently clear() needs to run before anything else...
66+
ls_helpers.clear()
67+
exec("set rtp+=" .. os.getenv("LUASNIP_SOURCE"))
68+
local function get_dedent_result(input_string)
69+
local result = exec_lua(
70+
string.format(
71+
[[return require("luasnip.util.str").dedent("%s")]],
72+
input_string
73+
)
74+
)
75+
return result
76+
end
77+
78+
it("spaces at beginnig", function()
79+
local input_table = {
80+
" line1",
81+
" ",
82+
" line3",
83+
" line4",
84+
}
85+
local input_string = table.concat(input_table, [[\n]])
86+
local expect_table = {
87+
"line1",
88+
"",
89+
" line3",
90+
" line4",
91+
}
92+
local expected = table.concat(expect_table, "\n")
93+
94+
local result = get_dedent_result(input_string)
95+
96+
assert.are.same(expected, result)
97+
end)
98+
it("tabs at beginnig", function()
99+
local input_table = {
100+
[[\t\tline1]],
101+
[[\t\t]],
102+
[[\t\t\tline3]],
103+
[[\t\t\t\tline4]],
104+
}
105+
local input_string = table.concat(input_table, [[\n]])
106+
local expect_table = {
107+
"line1",
108+
"",
109+
"\tline3",
110+
"\t\tline4",
111+
}
112+
local expected = table.concat(expect_table, "\n")
113+
114+
local result = get_dedent_result(input_string)
115+
116+
assert.are.same(expected, result)
117+
end)
118+
it("tabs & spaces at beginnig", function()
119+
local input_table = {
120+
[[\t\t line1]],
121+
[[\t\t ]],
122+
[[\t\t \t line3]],
123+
[[\t\t \t\t line4]],
124+
}
125+
local input_string = table.concat(input_table, [[\n]])
126+
local expect_table = {
127+
"line1",
128+
"",
129+
"\t line3",
130+
"\t\t line4",
131+
}
132+
local expected = table.concat(expect_table, "\n")
133+
134+
local result = get_dedent_result(input_string)
135+
136+
assert.are.same(expected, result)
137+
end)
138+
end)
139+
140+
describe("str.convert_indent", function()
141+
-- apparently clear() needs to run before anything else...
142+
ls_helpers.clear()
143+
exec("set rtp+=" .. os.getenv("LUASNIP_SOURCE"))
144+
local function get_convert_indent_result(input_string, indent_string)
145+
local result = exec_lua(
146+
string.format(
147+
[[return require("luasnip.util.str").convert_indent("%s", "%s")]],
148+
input_string,
149+
indent_string
150+
)
151+
)
152+
return result
153+
end
154+
155+
it("two spaces to tab", function()
156+
local input_table = {
157+
"line1: no indent",
158+
"",
159+
" line3: 1 indent",
160+
" line4: 2 indent",
161+
}
162+
local input_string = table.concat(input_table, [[\n]])
163+
local indent_string = " "
164+
local expect_table = {
165+
"line1: no indent",
166+
"",
167+
"\tline3: 1 indent",
168+
"\t\tline4: 2 indent",
169+
}
170+
local expected = table.concat(expect_table, "\n")
171+
172+
local result = get_convert_indent_result(input_string, indent_string)
173+
174+
assert.are.same(expected, result)
175+
end)
176+
it([[literal \t to tab]], function()
177+
local input_table = {
178+
"line1: no indent",
179+
"",
180+
[[\\tline3: 1 indent]],
181+
[[\\t\\tline4: 2 indent]],
182+
}
183+
local input_string = table.concat(input_table, [[\n]])
184+
local indent_string = [[\\t]]
185+
local expect_table = {
186+
"line1: no indent",
187+
"",
188+
"\tline3: 1 indent",
189+
"\t\tline4: 2 indent",
190+
}
191+
local expected = table.concat(expect_table, "\n")
192+
193+
local result = get_convert_indent_result(input_string, indent_string)
194+
195+
assert.are.same(expected, result)
196+
end)
197+
end)

0 commit comments

Comments
 (0)