Skip to content

Commit

Permalink
Merge pull request L3MON4D3#285 from leiserfg/on-the-fly
Browse files Browse the repository at this point in the history
Add On the fly snippets.
  • Loading branch information
L3MON4D3 authored Feb 13, 2022
2 parents 07a68bc + 0e7097b commit 0844072
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 1 deletion.
27 changes: 27 additions & 0 deletions DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,33 @@ insert 1 appended to itself, but the second jump will lead to it, making it
easy to override the generated text.
The text will only be changed when a argnode updates it.

## On The Fly snippets
You can create snippets that are not for being used all the time but only
in a single session.

This behaves as an "operator" takes what is in a register and transforms it into a
snippet using words prefixed as $ as inputs or copies (depending if the same word appears
more than once). You can escape $ by repeating it.

In order to use add something like this to your config:
```vim
vnoremap <c-f> "ec<C-\><C-O>:lua require('luasnip.extras.otf').on_the_fly()<cr>
inoremap <c-f> <C-\><C-O>"e:lua require('luasnip.extras.otf').on_the_fly()<cr>
```

Notice that you can use your own mapping instead of <c-f> and you can pick another register
instead of `"p`. You can even use it several times, as if it where a macro if you add several
mapppings like:
```vim
; For register a
vnoremap <c-f>a "ac<C-\><C-O>:lua require('luasnip.extras.otf').on_the_fly()<cr
inoremap <c-f>a <C-\><C-O>"a:lua require('luasnip.extras.otf').on_the_fly()<cr>
; For register b
vnoremap <c-f>a "bc<C-\><C-O>:lua require('luasnip.extras.otf').on_the_fly()<cr
inoremap <c-f>b <C-\><C-O>"b:lua require('luasnip.extras.otf').on_the_fly()<cr>
```

# LSP-SNIPPETS

Expand Down
29 changes: 29 additions & 0 deletions doc/luasnip.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ CONTENTS *luasnip-content
10. RESTORENODE..............................................|luasnip-restorenode|
11. ABSOLUTE_INDEXER....................................|luasnip-absolute_indexer|
12. EXTRAS........................................................|luasnip-extras|
12.1. On The Fly snippets........................|luasnip-on_the_fly_snippets|
13. LSP-SNIPPETS............................................|luasnip-lsp-snippets|
14. VARIABLES..................................................|luasnip-variables|
15. VSCODE SNIPPETS LOADER........................|luasnip-vscode_snippets_loader|
Expand Down Expand Up @@ -748,6 +749,34 @@ Examples:
easy to override the generated text.
The text will only be changed when a argnode updates it.

--------------------------------------------------------------------------------
ON THE FLY SNIPPETS *luasnip-on_the_fly_snippets*

You can create snippets that are not for being used all the time but only
in a single session.

This behaves as an "operator" takes what is in a register and transforms it into a
snippet using words prefixed as $ as inputs or copies (depending if the same word appears
more than once). You can escape $ by repeating it.

In order to use add something like this to your config:
>
vnoremap <c-f> "ec<C-\><C-O>:lua require('luasnip.extras.otf').on_the_fly()<cr>
inoremap <c-f> <C-\><C-O>"e:lua require('luasnip.extras.otf').on_the_fly()<cr>
<

Notice that you can use your own mapping instead of and you can pick another register
instead of `"p`. You can even use it several times, as if it where a macro if you add several
mapppings like:
>
; For register a
vnoremap <c-f>a "ac<C-\><C-O>:lua require('luasnip.extras.otf').on_the_fly()<cr
inoremap <c-f>a <C-\><C-O>"a:lua require('luasnip.extras.otf').on_the_fly()<cr>
; For register b
vnoremap <c-f>a "bc<C-\><C-O>:lua require('luasnip.extras.otf').on_the_fly()<cr
inoremap <c-f>b <C-\><C-O>"b:lua require('luasnip.extras.otf').on_the_fly()<cr>
<

================================================================================
LSP-SNIPPETS *luasnip-lsp-snippets*

Expand Down
94 changes: 94 additions & 0 deletions lua/luasnip/extras/_parser_combinator.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
-- Minimal parser combinator,
-- only for internal use so not exposed elsewhere nor documented in the oficial doc
--
local M = {}

-- Consumes strings matching a pattern, generates the matched string
function M.pattern(pat)
return function(text, pos)
local s, e = text:find(pat, pos)

if s then
local v = text:sub(s, e)
return true, v, pos + #v
else
return false, nil, pos
end
end
end

-- Matches whatever `p matches and generates whatever p generates after
-- transforming it with `f
function M.map(p, f)
return function(text, pos)
local succ, val, new_pos = p(text, pos)
if succ then
return true, f(val), new_pos
end
return false, nil, pos
end
end

-- Matches and generates the same as the first of it's children that matches something
function M.any(...)
local parsers = { ... }
return function(text, pos)
for _, p in ipairs(parsers) do
local succ, val, new_pos = p(text, pos)
if succ then
return true, val, new_pos
end
end
return false, nil, pos
end
end

-- Matches all what its children do in sequence, generates a table of its children generations
function M.seq(...)
local parsers = { ... }
return function(text, pos)
local original_pos = pos
local values = {}
for _, p in ipairs(parsers) do
local succ, val, new_pos = p(text, pos)
pos = new_pos
if not succ then
return false, nil, original_pos
end
table.insert(values, val)
end
return true, values, pos
end
end

-- Matches cero or more times what it child do in sequence, generates a table with those generations
function M.star(p)
return function(text, pos)
local len = #text
local values = {}

while pos <= len do
local succ, val, new_pos = p(text, pos)
if succ then
pos = new_pos
table.insert(values, val)
else
break
end
end
return #values > 0, values, pos
end
end

-- Consumes a literal string, does not generates
function M.literal(t)
return function(text, pos)
if text:sub(pos, pos + #t - 1) == t then
return true, nil, pos + #t
else
return false, text:sub(pos, pos + #t), pos + #t
end
end
end

return M
89 changes: 89 additions & 0 deletions lua/luasnip/extras/otf.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
local ls = require("luasnip")
local cp = require("luasnip.util.functions").copy
local p = require("luasnip.extras._parser_combinator")
local dedent = require("luasnip.util.str").dedent
local M = {}

local T = { EOL = "EOL", TXT = "TXT", INP = "INP" }

local chunk = p.any(
p.map(p.literal("$$"), function()
return { T.TXT, "$" }
end),
p.map(p.literal("\n"), function()
return { T.EOL }
end),
p.map(p.seq(p.literal("$"), p.pattern("%w+")), function(c)
return { T.INP, c[1] }
end),
p.map(p.pattern("[^\n$]*"), function(c)
return { T.TXT, c }
end)
)

M._snippet_chunks = p.star(chunk)

function M._txt_to_snip(txt)
local t = ls.t
local s = ls.s
local i = ls.i
local f = ls.f
txt = dedent(txt)

-- The parser does not handle empty strings
if txt == "" then
return s("", t({ "" }))
end

local _, chunks, _ = M._snippet_chunks(txt, 1)

local current_text_arg = { "" }
local nodes = {}
local know_inputs = {}
local last_input_pos = 0

for _, part in ipairs(chunks) do
if part[1] == T.TXT then
current_text_arg[#current_text_arg] = current_text_arg[#current_text_arg]
.. part[2]
elseif #current_text_arg > 1 or current_text_arg[1] ~= "" then
table.insert(nodes, t(current_text_arg))
current_text_arg = { "" }
end

if part[1] == T.EOL then
table.insert(current_text_arg, "")
elseif part[1] == T.INP then
local inp_pos = know_inputs[part[2]]
if inp_pos then
table.insert(nodes, f(cp, { inp_pos }))
else
last_input_pos = last_input_pos + 1
know_inputs[part[2]] = last_input_pos
table.insert(nodes, i(last_input_pos, part[2]))
end
end
end
if #current_text_arg > 1 or current_text_arg[1] ~= "" then
table.insert(nodes, t(current_text_arg))
end
return s("", nodes)
end

local last_snip = nil
local last_reg = nil

-- Create snippets On The Fly
-- It's advaisable not to use the default register as luasnip will probably
-- override it
function M.on_the_fly(regname)
regname = regname or ""
local reg = table.concat(vim.fn.getreg(regname, 1, true), "\n") -- Avoid eol in the last line
if last_reg ~= reg then
last_reg = reg
last_snip = M._txt_to_snip(reg)
end
ls.snip_expand(last_snip)
end

return M
2 changes: 1 addition & 1 deletion lua/luasnip/util/environ.lua
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ local table_env_vars = {
-- returns nil, but that should be alright.
-- If not, use metatable.
function Environ.is_table(key)
return table_env_vars.key
return table_env_vars[key]
end

return Environ
21 changes: 21 additions & 0 deletions lua/luasnip/util/str.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- Some string processing utility functions
local M = {}

function M.dedent(s)
local lst = vim.split(s, "\n")
if #lst > 0 then
local ind_size = math.huge
for i, _ in ipairs(lst) do
local i1, i2 = lst[i]:find("^%s*[^%s]")
if i1 and i2 < ind_size then
ind_size = i2
end
end
for i, _ in ipairs(lst) do
lst[i] = lst[i]:sub(ind_size, -1)
end
end
return table.concat(lst, "\n")
end

return M
42 changes: 42 additions & 0 deletions tests/unit/otf_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
local helpers = require("test.functional.helpers")(after_each)
local exec_lua = helpers.exec_lua

describe("luasnip.extra.otf", function()
local function check(test_name, input, output)
it(test_name, function()
assert.are.same(
output,
exec_lua(
[=[
local _, parts, _ = require("luasnip.extras.otf")._snippet_chunks(..., 1)
return parts
]=],
input
)
)
end)
end

helpers.exec("set rtp+=" .. os.getenv("LUASNIP_SOURCE"))

check("Only text", "one", { { "TXT", "one" } })
check("Text and inputs", "local $val = require'module'.$color", {
{ "TXT", "local " },
{ "INP", "val" },
{ "TXT", " = require'module'." },
{ "INP", "color" },
})

check(
"Multiline text with escapes",
"$something is more important than $$\nbut you can have both --$someone",
{
{ "INP", "something" },
{ "TXT", " is more important than " },
{ "TXT", "$" },
{ "EOL" },
{ "TXT", "but you can have both --" },
{ "INP", "someone" },
}
)
end)
22 changes: 22 additions & 0 deletions tests/unit/utils_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
local helpers = require("test.functional.helpers")(after_each)
local exec_lua = helpers.exec_lua

describe("luasnip.util.str:dedent", function()
local function check(test_name, input, output)
it(test_name, function()
assert.are.same(
output,
exec_lua(
'return require("luasnip.util.str").dedent([['
.. input
.. "]])"
)
)
end)
end

check("2 and 0", " one", "one")
check("0 and 2", "one\n two", "one\n two")
check("2 and 1", " one\n two", " one\ntwo")
check("2 and 2", " one\n two", "one\ntwo")
end)

0 comments on commit 0844072

Please sign in to comment.