Skip to content

Commit

Permalink
Merge branch 'master' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
L3MON4D3 authored Nov 30, 2024
2 parents 32fae27 + 4e58435 commit 6e141eb
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 1 deletion.
20 changes: 20 additions & 0 deletions DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,23 @@ ls.add_snippets("all", {
}, {
repeat_duplicates = true
}))
s("example5", fmt([[
line1: no indent
line3: 2 space -> 1 indent ('\t')
line4: 4 space -> 2 indent ('\t\t')
]], {}, {
indent_string = " "
}))
-- NOTE: [[\t]] means '\\t'
s("example6", fmt([[
line1: no indent
\tline3: '\\t' -> 1 indent ('\t')
\t\tline4: '\\t\\t' -> 2 indent ('\t\t')
]], {}, {
indent_string = [[\t]]
}))
})
```

Expand Down Expand Up @@ -1560,6 +1577,9 @@ any way, correspond to the jump-index of the nodes!
when passing multiline strings via `[[]]` (default true).
* `dedent`: remove indent common to all lines in `format`. Again, makes
passing multiline-strings a bit nicer (default true).
* `indent_string`: convert `indent_string` at beginning of each line to unit
indent ('\t'). This is applied after `dedent`. Useful when using
multiline string in `fmt`. (default empty string, disabled)
* `repeat_duplicates`: repeat nodes when a key is reused instead of copying
the node if it has a jump-index, refer to [Basics-Jump-Index](#jump-index) to
know which nodes have a jump-index (default false).
Expand Down
22 changes: 21 additions & 1 deletion doc/luasnip.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
*luasnip.txt* For NVIM v0.8.0 Last change: 2024 November 29
*luasnip.txt* For NVIM v0.8.0 Last change: 2024 November 30

==============================================================================
Table of Contents *luasnip-table-of-contents*
Expand Down Expand Up @@ -1459,6 +1459,23 @@ Simple example:
}, {
repeat_duplicates = true
}))
s("example5", fmt([[
line1: no indent

line3: 2 space -> 1 indent ('\t')
line4: 4 space -> 2 indent ('\t\t')
]], {}, {
indent_string = " "
}))
-- NOTE: [[\t]] means '\\t'
s("example6", fmt([[
line1: no indent

\tline3: '\\t' -> 1 indent ('\t')
\t\tline4: '\\t\\t' -> 2 indent ('\t\t')
]], {}, {
indent_string = [[\t]]
}))
})
<

Expand Down Expand Up @@ -1487,6 +1504,9 @@ any way, correspond to the jump-index of the nodes!
when passing multiline strings via `[[]]` (default true).
- `dedent`: remove indent common to all lines in `format`. Again, makes
passing multiline-strings a bit nicer (default true).
- `indent_string`: convert `indent_string` at beginning of each line to unit
indent (’). This is applied after `dedent`. Useful when using
multiline string in `fmt`. (default empty string, disabled)
- `repeat_duplicates`: repeat nodes when a key is reused instead of copying
the node if it has a jump-index, refer to |luasnip-basics-jump-index| to
know which nodes have a jump-index (default false).
Expand Down
3 changes: 3 additions & 0 deletions lua/luasnip/extras/fmt.lua
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,15 @@ end
-- opts: optional table
-- trim_empty: boolean, remove whitespace-only first/last lines, default true
-- dedent: boolean, remove all common indent in `str`, default true
-- indent_string: string, convert `indent_string` at beginning of each line to unit indent ('\t')
-- after applying `dedent`, default empty string (disabled)
-- ... the rest is passed to `interpolate`
-- Returns: list of snippet nodes
local function format_nodes(str, nodes, opts)
local defaults = {
trim_empty = true,
dedent = true,
indent_string = "",
}
opts = vim.tbl_extend("force", defaults, opts or {})

Expand Down
44 changes: 44 additions & 0 deletions lua/luasnip/util/str.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,46 @@ local function dedent(lines)
end
end

---Convert string `from` to unit indent
---@param lines string[]
---@param from string
---@param unit_indent string
local function convert_indent(lines, from, unit_indent)
local from_length = #from
if #lines == 0 or from_length == 0 or from == unit_indent then
return
end

local from_bytes = { string.byte(from, 1, from_length) }
for i = 1, #lines do
local line_bytes = { string.byte(lines[i], 1, #lines[i]) }
local line_length = #line_bytes
local indent_count = 0
local j, k = 1, 1
while j <= line_length and line_bytes[j] == from_bytes[k] do
if k == from_length then
indent_count = indent_count + 1
end
j = j + 1
k = k % from_length + 1
end
if indent_count > 0 then
lines[i] = string.format(
"%s%s",
string.rep(unit_indent, indent_count),
string.sub(lines[i], from_length * indent_count + 1)
)
end
end
end

---Applies opts to lines.
---lines is modified in-place.
---@param lines string[].
---@param options table, required, can have values:
--- - trim_empty: removes empty first and last lines.
--- - dedent: removes indent common to all lines.
--- - indent_string: an unit indent at beginning of each line after applying `dedent`, default empty string (disabled)
function M.process_multiline(lines, options)
if options.trim_empty then
if lines[1]:match("^%s*$") then
Expand All @@ -37,6 +71,10 @@ function M.process_multiline(lines, options)
if options.dedent then
dedent(lines)
end

if options.indent_string and #options.indent_string > 0 then
convert_indent(lines, options.indent_string, "\t")
end
end

function M.dedent(s)
Expand All @@ -45,6 +83,12 @@ function M.dedent(s)
return table.concat(lst, "\n")
end

function M.convert_indent(s, indent_string)
local lst = vim.split(s, "\n")
convert_indent(lst, indent_string, "\t")
return table.concat(lst, "\n")
end

local function is_escaped(s, indx)
local count = 0
for i = indx - 1, 1, -1 do
Expand Down
135 changes: 135 additions & 0 deletions tests/unit/str_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,138 @@ describe("str.unescaped_pairs", function()
{ { 1, 3 }, { 5, 8 }, { 9, 11 } }
)
end)

describe("str.dedent", function()
-- apparently clear() needs to run before anything else...
ls_helpers.clear()
exec("set rtp+=" .. os.getenv("LUASNIP_SOURCE"))
local function get_dedent_result(input_string)
local result = exec_lua(
string.format(
[[return require("luasnip.util.str").dedent("%s")]],
input_string
)
)
return result
end

it("spaces at beginnig", function()
local input_table = {
" line1",
" ",
" line3",
" line4",
}
local input_string = table.concat(input_table, [[\n]])
local expect_table = {
"line1",
"",
" line3",
" line4",
}
local expected = table.concat(expect_table, "\n")

local result = get_dedent_result(input_string)

assert.are.same(expected, result)
end)
it("tabs at beginnig", function()
local input_table = {
[[\t\tline1]],
[[\t\t]],
[[\t\t\tline3]],
[[\t\t\t\tline4]],
}
local input_string = table.concat(input_table, [[\n]])
local expect_table = {
"line1",
"",
"\tline3",
"\t\tline4",
}
local expected = table.concat(expect_table, "\n")

local result = get_dedent_result(input_string)

assert.are.same(expected, result)
end)
it("tabs & spaces at beginnig", function()
local input_table = {
[[\t\t line1]],
[[\t\t ]],
[[\t\t \t line3]],
[[\t\t \t\t line4]],
}
local input_string = table.concat(input_table, [[\n]])
local expect_table = {
"line1",
"",
"\t line3",
"\t\t line4",
}
local expected = table.concat(expect_table, "\n")

local result = get_dedent_result(input_string)

assert.are.same(expected, result)
end)
end)

describe("str.convert_indent", function()
-- apparently clear() needs to run before anything else...
ls_helpers.clear()
exec("set rtp+=" .. os.getenv("LUASNIP_SOURCE"))
local function get_convert_indent_result(input_string, indent_string)
local result = exec_lua(
string.format(
[[return require("luasnip.util.str").convert_indent("%s", "%s")]],
input_string,
indent_string
)
)
return result
end

it("two spaces to tab", function()
local input_table = {
"line1: no indent",
"",
" line3: 1 indent",
" line4: 2 indent",
}
local input_string = table.concat(input_table, [[\n]])
local indent_string = " "
local expect_table = {
"line1: no indent",
"",
"\tline3: 1 indent",
"\t\tline4: 2 indent",
}
local expected = table.concat(expect_table, "\n")

local result = get_convert_indent_result(input_string, indent_string)

assert.are.same(expected, result)
end)
it([[literal \t to tab]], function()
local input_table = {
"line1: no indent",
"",
[[\\tline3: 1 indent]],
[[\\t\\tline4: 2 indent]],
}
local input_string = table.concat(input_table, [[\n]])
local indent_string = [[\\t]]
local expect_table = {
"line1: no indent",
"",
"\tline3: 1 indent",
"\t\tline4: 2 indent",
}
local expected = table.concat(expect_table, "\n")

local result = get_convert_indent_result(input_string, indent_string)

assert.are.same(expected, result)
end)
end)

0 comments on commit 6e141eb

Please sign in to comment.