mirror of
https://github.com/L3MON4D3/LuaSnip
synced 2024-09-16 21:54:03 +02:00
Merge branch 'master' into make_jsregexp_simplify
This commit is contained in:
commit
b72ae3866f
21 changed files with 850 additions and 169 deletions
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -1,3 +1,3 @@
|
|||
[submodule "deps/jsregexp"]
|
||||
path = deps/jsregexp
|
||||
url = https://github.com/kmarius/jsregexp/
|
||||
url = ../../kmarius/jsregexp/
|
||||
|
|
109
DOC.md
109
DOC.md
|
@ -212,6 +212,8 @@ s({trig="trigger"}, {})
|
|||
LuaSnip on snippet expansion (and thus has access to the matched trigger and
|
||||
captures), while `show_condition` is (should be) evaluated by the
|
||||
completion engines when scanning for available snippet candidates.
|
||||
- `filetype`: `string`, the filetype of the snippet.
|
||||
This overrides the filetype the snippet is added (via `add_snippet`) as.
|
||||
|
||||
- `nodes`: A single node or a list of nodes. The nodes that make up the
|
||||
snippet.
|
||||
|
@ -2150,7 +2152,7 @@ Luasnip is capable of loading snippets from different formats, including both
|
|||
the well-established VSCode and SnipMate format, as well as plain Lua files for
|
||||
snippets written in Lua.
|
||||
|
||||
All loaders share a similar interface:
|
||||
All loaders (except the vscode-standalone-loader) share a similar interface:
|
||||
`require("luasnip.loaders.from_{vscode,snipmate,lua}").{lazy_,}load(opts:table|nil)`
|
||||
|
||||
where `opts` can contain the following keys:
|
||||
|
@ -2181,7 +2183,7 @@ filetype is changed luasnip actually loads `lazy_load`ed snippets for the
|
|||
filetypes associated with this buffer. This association can be changed by
|
||||
customizing `load_ft_func` in `setup`: the option takes a function that, passed
|
||||
a `bufnr`, returns the filetypes that should be loaded (`fn(bufnr) -> filetypes
|
||||
(string[])`)).
|
||||
(string[])`)).
|
||||
|
||||
All of the loaders support reloading, so simply editing any file contributing
|
||||
snippets will reload its snippets (only in the session the file was edited in;
|
||||
|
@ -2191,6 +2193,46 @@ For easy editing of these files, LuaSnip provides a `vim.ui.select`-based dialog
|
|||
([Loaders-edit_snippets](#edit_snippets)) where first the filetype, and then the
|
||||
file can be selected.
|
||||
|
||||
### Snippet-specific filetypes
|
||||
Some loaders (vscode,lua) support giving snippets generated in some file their
|
||||
own filetype (vscode via `scope`, lua via the underlying `filetype`-option for
|
||||
snippets). These snippet-specific filetypes are not considered when determining
|
||||
which files to `lazy_load` for some filetype, this is exclusively determined by
|
||||
the `language` associated with a file in vscodes' `package.json`, and the
|
||||
file/directory-name in lua.
|
||||
This can be resolved relatively easily in vscode, where the `language`
|
||||
advertised in `package.json` can just be a superset of the `scope`s in the file.
|
||||
Another simplistic solution is to set the language to `all` (in lua, it might
|
||||
make sense to create a directory `luasnippets/all/*.lua` to group these files
|
||||
together).
|
||||
Another approach is to modify `load_ft_func` to load a custom filetype if the
|
||||
snippets should be activated, and store the snippets in a file for that
|
||||
filetype. This can be used to group snippets by e.g. framework, and load them
|
||||
once a file belonging to such a framework is edited.
|
||||
|
||||
**Example**:
|
||||
`react.lua`
|
||||
```lua
|
||||
return {
|
||||
s({filetype = "css", trig = ...}, ...),
|
||||
s({filetype = "html", trig = ...}, ...),
|
||||
s({filetype = "js", trig = ...}, ...),
|
||||
}
|
||||
```
|
||||
|
||||
`luasnip_config.lua`
|
||||
```lua
|
||||
load_ft_func = function(bufnr)
|
||||
if "<bufnr-in-react-framework>" then
|
||||
-- will load `react.lua` for this buffer
|
||||
return {"react"}
|
||||
else
|
||||
return require("luasnip.extras.filetype_functions").from_filetype_load
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* LuaSnip uses `all` as the global filetype. As most snippet collections don't
|
||||
|
@ -2313,6 +2355,69 @@ require("luasnip.loaders.from_vscode").lazy_load({paths = "~/.config/nvim/my_sni
|
|||
require("luasnip.loaders.from_vscode").load({paths = "./my_snippets"})
|
||||
```
|
||||
|
||||
### Standalone
|
||||
Beside snippet-libraries provided by packages, vscode also supports another
|
||||
format which can be used for project-local snippets, or user-defined snippets,
|
||||
`.code-snippets`.
|
||||
|
||||
The layout of these files is almost identical to that of the package-provided
|
||||
snippets, but there is one additional field supported in the
|
||||
snippet-definitions, `scope`, with which the filetype of the snippet can be set.
|
||||
If `scope` is not set, the snippet will be added to the global filetype (`all`).
|
||||
|
||||
`require("luasnip.loaders.from_vscode").load_standalone(opts)`
|
||||
|
||||
- `opts`: `table`, can contain the following keys:
|
||||
- `path`: `string`, Path to the `*.code-snippets`-file that should be loaded.
|
||||
Just like the paths in `load`, this one can begin with a `"~/"` to be
|
||||
relative to `$HOME`, and a `"./"` to be relative to the
|
||||
neovim-config-directory.
|
||||
- `{override,default}_priority`: These keys are passed straight to the
|
||||
`add_snippets`-calls (documented in [API](#api)) and can be used to change
|
||||
the priority of the loaded snippets.
|
||||
|
||||
**Example**:
|
||||
`a.code-snippets`:
|
||||
```jsonc
|
||||
{
|
||||
// a comment, since `.code-snippets` may contain jsonc.
|
||||
"c/cpp-snippet": {
|
||||
"prefix": [
|
||||
"trigger1",
|
||||
"trigger2"
|
||||
],
|
||||
"body": [
|
||||
"this is $1",
|
||||
"my snippet $2"
|
||||
],
|
||||
"description": "A description of the snippet.",
|
||||
"scope": "c,cpp"
|
||||
},
|
||||
"python-snippet": {
|
||||
"prefix": "trig",
|
||||
"body": [
|
||||
"this is $1",
|
||||
"a different snippet $2"
|
||||
],
|
||||
"description": "Another snippet-description.",
|
||||
"scope": "python"
|
||||
},
|
||||
"global snippet": {
|
||||
"prefix": "trigg",
|
||||
"body": [
|
||||
"this is $1",
|
||||
"the last snippet $2"
|
||||
],
|
||||
"description": "One last snippet-description.",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This file can be loaded by calling
|
||||
```lua
|
||||
require("luasnip.loaders.from_vscode").load_standalone({path = "a.code-snippets"})
|
||||
```
|
||||
|
||||
## SNIPMATE
|
||||
|
||||
Luasnip does not support the full snipmate format: Only `./{ft}.snippets` and
|
||||
|
|
112
doc/luasnip.txt
112
doc/luasnip.txt
|
@ -284,6 +284,8 @@ The most direct way to define snippets is `s`:
|
|||
LuaSnip on snippet expansion (and thus has access to the matched trigger and
|
||||
captures), while `show_condition` is (should be) evaluated by the
|
||||
completion engines when scanning for available snippet candidates.
|
||||
- `filetype`: `string`, the filetype of the snippet.
|
||||
This overrides the filetype the snippet is added (via `add_snippet`) as.
|
||||
- `nodes`: A single node or a list of nodes. The nodes that make up the snippet.
|
||||
- `opts`: A table with the following valid keys:
|
||||
- `callbacks`: Contains functions that are called upon entering/leaving a node of
|
||||
|
@ -2015,7 +2017,7 @@ Luasnip is capable of loading snippets from different formats, including both
|
|||
the well-established VSCode and SnipMate format, as well as plain Lua files for
|
||||
snippets written in Lua.
|
||||
|
||||
All loaders share a similar interface:
|
||||
All loaders (except the vscode-standalone-loader) share a similar interface:
|
||||
`require("luasnip.loaders.from_{vscode,snipmate,lua}").{lazy_,}load(opts:table|nil)`
|
||||
|
||||
where `opts` can contain the following keys:
|
||||
|
@ -2057,6 +2059,47 @@ dialog (|luasnip-loaders-edit_snippets|) where first the filetype, and then the
|
|||
file can be selected.
|
||||
|
||||
|
||||
SNIPPET-SPECIFIC FILETYPES ~
|
||||
|
||||
Some loaders (vscode,lua) support giving snippets generated in some file their
|
||||
own filetype (vscode via `scope`, lua via the underlying `filetype`-option for
|
||||
snippets). These snippet-specific filetypes are not considered when determining
|
||||
which files to `lazy_load` for some filetype, this is exclusively determined by
|
||||
the `language` associated with a file in vscodes’ `package.json`, and the
|
||||
file/directory-name in lua. This can be resolved relatively easily in vscode,
|
||||
where the `language` advertised in `package.json` can just be a superset of the
|
||||
`scope`s in the file. Another simplistic solution is to set the language to
|
||||
`all` (in lua, it might make sense to create a directory
|
||||
`luasnippets/all/*.lua` to group these files together). Another approach is to
|
||||
modify `load_ft_func` to load a custom filetype if the snippets should be
|
||||
activated, and store the snippets in a file for that filetype. This can be used
|
||||
to group snippets by e.g. framework, and load them once a file belonging to
|
||||
such a framework is edited.
|
||||
|
||||
**Example**: `react.lua`
|
||||
|
||||
>lua
|
||||
return {
|
||||
s({filetype = "css", trig = ...}, ...),
|
||||
s({filetype = "html", trig = ...}, ...),
|
||||
s({filetype = "js", trig = ...}, ...),
|
||||
}
|
||||
<
|
||||
|
||||
`luasnip_config.lua`
|
||||
|
||||
>lua
|
||||
load_ft_func = function(bufnr)
|
||||
if "<bufnr-in-react-framework>" then
|
||||
-- will load `react.lua` for this buffer
|
||||
return {"react"}
|
||||
else
|
||||
return require("luasnip.extras.filetype_functions").from_filetype_load
|
||||
end
|
||||
end
|
||||
<
|
||||
|
||||
|
||||
TROUBLESHOOTING *luasnip-loaders-troubleshooting*
|
||||
|
||||
- LuaSnip uses `all` as the global filetype. As most snippet collections don’t
|
||||
|
@ -2185,6 +2228,73 @@ This collection can be loaded with any of
|
|||
<
|
||||
|
||||
|
||||
STANDALONE ~
|
||||
|
||||
Beside snippet-libraries provided by packages, vscode also supports another
|
||||
format which can be used for project-local snippets, or user-defined snippets,
|
||||
`.code-snippets`.
|
||||
|
||||
The layout of these files is almost identical to that of the package-provided
|
||||
snippets, but there is one additional field supported in the
|
||||
snippet-definitions, `scope`, with which the filetype of the snippet can be
|
||||
set. If `scope` is not set, the snippet will be added to the global filetype
|
||||
(`all`).
|
||||
|
||||
`require("luasnip.loaders.from_vscode").load_standalone(opts)`
|
||||
|
||||
- `opts`: `table`, can contain the following keys:
|
||||
- `path`: `string`, Path to the `*.code-snippets`-file that should be loaded.
|
||||
Just like the paths in `load`, this one can begin with a `"~/"` to be
|
||||
relative to `$HOME`, and a `"./"` to be relative to the
|
||||
neovim-config-directory.
|
||||
- `{override,default}_priority`: These keys are passed straight to the
|
||||
`add_snippets`-calls (documented in |luasnip-api|) and can be used to change
|
||||
the priority of the loaded snippets.
|
||||
|
||||
**Example**: `a.code-snippets`:
|
||||
|
||||
>jsonc
|
||||
{
|
||||
// a comment, since `.code-snippets` may contain jsonc.
|
||||
"c/cpp-snippet": {
|
||||
"prefix": [
|
||||
"trigger1",
|
||||
"trigger2"
|
||||
],
|
||||
"body": [
|
||||
"this is $1",
|
||||
"my snippet $2"
|
||||
],
|
||||
"description": "A description of the snippet.",
|
||||
"scope": "c,cpp"
|
||||
},
|
||||
"python-snippet": {
|
||||
"prefix": "trig",
|
||||
"body": [
|
||||
"this is $1",
|
||||
"a different snippet $2"
|
||||
],
|
||||
"description": "Another snippet-description.",
|
||||
"scope": "python"
|
||||
},
|
||||
"global snippet": {
|
||||
"prefix": "trigg",
|
||||
"body": [
|
||||
"this is $1",
|
||||
"the last snippet $2"
|
||||
],
|
||||
"description": "One last snippet-description.",
|
||||
}
|
||||
}
|
||||
<
|
||||
|
||||
This file can be loaded by calling
|
||||
|
||||
>lua
|
||||
require("luasnip.loaders.from_vscode").load_standalone({path = "a.code-snippets"})
|
||||
<
|
||||
|
||||
|
||||
SNIPMATE *luasnip-loaders-snipmate*
|
||||
|
||||
Luasnip does not support the full snipmate format: Only `./{ft}.snippets` and
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
local Source = require("luasnip.session.snippet_collection.source")
|
||||
local util = require("luasnip.util.util")
|
||||
|
||||
-- stylua: ignore
|
||||
local tsquery_parse =
|
||||
|
@ -54,8 +55,8 @@ local function range_highlight(line_start, line_end, hl_duration_ms)
|
|||
end
|
||||
end
|
||||
|
||||
local function json_find_snippet_definition(bufnr, extension, snippet_name)
|
||||
local parser_ok, parser = pcall(vim.treesitter.get_parser, bufnr, extension)
|
||||
local function json_find_snippet_definition(bufnr, filetype, snippet_name)
|
||||
local parser_ok, parser = pcall(vim.treesitter.get_parser, bufnr, filetype)
|
||||
if not parser_ok then
|
||||
error("Error while getting parser: " .. parser)
|
||||
end
|
||||
|
@ -113,7 +114,12 @@ function M.jump_to_snippet(snip, opts)
|
|||
end
|
||||
|
||||
local fcall_range
|
||||
if vim.api.nvim_buf_get_name(0):match("%.lua$") then
|
||||
local ft = util.ternary(
|
||||
vim.bo[0].filetype ~= "",
|
||||
vim.bo[0].filetype,
|
||||
vim.api.nvim_buf_get_name(0):match("%.([^%.]+)$")
|
||||
)
|
||||
if ft == "lua" then
|
||||
if source.line then
|
||||
-- in lua-file, can get region of definition via treesitter.
|
||||
-- 0: current buffer.
|
||||
|
@ -133,11 +139,9 @@ function M.jump_to_snippet(snip, opts)
|
|||
return
|
||||
end
|
||||
-- matches *.json or *.jsonc.
|
||||
elseif vim.api.nvim_buf_get_name(0):match("%.jsonc?$") then
|
||||
local extension = vim.api.nvim_buf_get_name(0):match("jsonc?$")
|
||||
elseif ft == "json" or ft == "jsonc" then
|
||||
local ok
|
||||
ok, fcall_range =
|
||||
pcall(json_find_snippet_definition, 0, extension, snip.name)
|
||||
ok, fcall_range = pcall(json_find_snippet_definition, 0, ft, snip.name)
|
||||
if not ok then
|
||||
print(
|
||||
"Could not determine range of snippet-definition: "
|
||||
|
|
|
@ -40,11 +40,20 @@ local function new_cache()
|
|||
lazy_loaded_ft = { all = true },
|
||||
|
||||
-- key is file type, value are normalized!! paths of .snippets files.
|
||||
-- shall contain all files loaded by any loader.
|
||||
ft_paths = {},
|
||||
|
||||
-- key is _normalized!!!!_ file path, value are loader-specific.
|
||||
-- Might contain the snippets from the file, or the filetype(s) it
|
||||
-- contributes to.
|
||||
--
|
||||
-- for vscode:
|
||||
-- stores {
|
||||
-- snippets, -- the snippets provided by the file
|
||||
-- filetype_add_opts, -- add_opts for some filetype
|
||||
-- filetypes -- filetypes for which this file is active (important for
|
||||
-- reload).
|
||||
-- }
|
||||
path_snippets = {},
|
||||
}, {
|
||||
__index = Cache,
|
||||
|
@ -52,13 +61,15 @@ local function new_cache()
|
|||
end
|
||||
|
||||
local M = {
|
||||
vscode = new_cache(),
|
||||
vscode_packages = new_cache(),
|
||||
vscode_standalone = new_cache(),
|
||||
snipmate = new_cache(),
|
||||
lua = new_cache(),
|
||||
}
|
||||
|
||||
function M.cleanup()
|
||||
M.vscode:clean()
|
||||
M.vscode_packages:clean()
|
||||
M.vscode_standalone:clean()
|
||||
M.snipmate:clean()
|
||||
M.lua:clean()
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
local ls = require("luasnip")
|
||||
local cache = require("luasnip.loaders._caches").vscode
|
||||
local package_cache = require("luasnip.loaders._caches").vscode_packages
|
||||
local standalone_cache = require("luasnip.loaders._caches").vscode_standalone
|
||||
local util = require("luasnip.util.util")
|
||||
local loader_util = require("luasnip.loaders.util")
|
||||
local Path = require("luasnip.util.path")
|
||||
|
@ -7,10 +8,13 @@ local sp = require("luasnip.nodes.snippetProxy")
|
|||
local log = require("luasnip.util.log").new("vscode-loader")
|
||||
local session = require("luasnip.session")
|
||||
local source = require("luasnip.session.snippet_collection.source")
|
||||
local multisnippet = require("luasnip.nodes.multiSnippet")
|
||||
local duplicate = require("luasnip.nodes.duplicate")
|
||||
|
||||
local json_decoders = {
|
||||
json = util.json_decode,
|
||||
jsonc = require("luasnip.util.jsonc").decode,
|
||||
["code-snippets"] = require("luasnip.util.jsonc").decode,
|
||||
}
|
||||
|
||||
local function read_json(fname)
|
||||
|
@ -21,9 +25,9 @@ local function read_json(fname)
|
|||
end
|
||||
|
||||
local fname_extension = Path.extension(fname)
|
||||
if fname_extension ~= "json" and fname_extension ~= "jsonc" then
|
||||
if json_decoders[fname_extension] == nil then
|
||||
log.error(
|
||||
"`%s` was expected to have file-extension either `json` or `jsonc`, but doesn't.",
|
||||
"`%s` was expected to have file-extension either `json`, `jsonc` or `code-snippets`, but doesn't.",
|
||||
fname
|
||||
)
|
||||
return nil
|
||||
|
@ -39,110 +43,122 @@ local function read_json(fname)
|
|||
end
|
||||
end
|
||||
|
||||
-- return all snippets in `file`.
|
||||
local function get_file_snippets(file)
|
||||
local lang_snips = {}
|
||||
local auto_lang_snips = {}
|
||||
-- since most snippets we load don't have a scope-field, we just insert this here by default.
|
||||
local snippets = {}
|
||||
|
||||
local snippet_set_data = read_json(file)
|
||||
if snippet_set_data == nil then
|
||||
log.error("Reading json from file `%s` failed, skipping it.", file)
|
||||
return {}, {}
|
||||
return {}
|
||||
end
|
||||
|
||||
for name, parts in pairs(snippet_set_data) do
|
||||
local body = type(parts.body) == "string" and parts.body
|
||||
or table.concat(parts.body, "\n")
|
||||
|
||||
-- There are still some snippets that fail while loading
|
||||
pcall(function()
|
||||
-- Sometimes it's a list of prefixes instead of a single one
|
||||
local prefixes = type(parts.prefix) == "table" and parts.prefix
|
||||
or { parts.prefix }
|
||||
for _, prefix in ipairs(prefixes) do
|
||||
local ls_conf = parts.luasnip or {}
|
||||
local ls_conf = parts.luasnip or {}
|
||||
|
||||
local snip = sp({
|
||||
trig = prefix,
|
||||
name = name,
|
||||
dscr = parts.description or name,
|
||||
wordTrig = ls_conf.wordTrig,
|
||||
priority = ls_conf.priority,
|
||||
}, body)
|
||||
-- we may generate multiple interfaces to the same snippet
|
||||
-- (different filetype, different triggers)
|
||||
|
||||
if session.config.loaders_store_source then
|
||||
-- only know file, not line or line_end.
|
||||
snip._source = source.from_location(file)
|
||||
end
|
||||
-- context common to all snippets generated here.
|
||||
local common_context = {
|
||||
name = name,
|
||||
dscr = parts.description or name,
|
||||
wordTrig = ls_conf.wordTrig,
|
||||
priority = ls_conf.priority,
|
||||
snippetType = ls_conf.autotrigger and "autosnippet" or "snippet",
|
||||
}
|
||||
|
||||
if ls_conf.autotrigger then
|
||||
table.insert(auto_lang_snips, snip)
|
||||
else
|
||||
table.insert(lang_snips, snip)
|
||||
end
|
||||
-- Sometimes it's a list of prefixes instead of a single one
|
||||
local prefixes = type(parts.prefix) == "table" and parts.prefix
|
||||
or { parts.prefix }
|
||||
|
||||
-- vscode documents `,`, but `.` also works.
|
||||
-- an entry `false` in this list will cause a `ft=nil` for the snippet.
|
||||
local filetypes = parts.scope and vim.split(parts.scope, "[.,]")
|
||||
or { false }
|
||||
|
||||
local contexts = {}
|
||||
for _, prefix in ipairs(prefixes) do
|
||||
for _, filetype in ipairs(filetypes) do
|
||||
table.insert(
|
||||
contexts,
|
||||
{ filetype = filetype or nil, trig = prefix }
|
||||
)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return lang_snips, auto_lang_snips
|
||||
end
|
||||
local snip
|
||||
if #contexts > 1 then
|
||||
-- only construct multisnippet if it is actually necessary.
|
||||
contexts.common = common_context
|
||||
snip = multisnippet._raw_ms(contexts, sp(nil, body), {})
|
||||
elseif #contexts == 1 then
|
||||
-- have to add options from common context to the trig/filetype-context.
|
||||
snip = sp(vim.tbl_extend("keep", contexts[1], common_context), body)
|
||||
end
|
||||
|
||||
local function load_snippet_files(lang, files, add_opts)
|
||||
for _, file in ipairs(files) do
|
||||
if Path.exists(file) then
|
||||
local lang_snips, auto_lang_snips
|
||||
|
||||
local cached_path = cache.path_snippets[file]
|
||||
if cached_path then
|
||||
lang_snips = vim.deepcopy(cached_path.snippets)
|
||||
auto_lang_snips = vim.deepcopy(cached_path.autosnippets)
|
||||
cached_path.fts[lang] = true
|
||||
else
|
||||
lang_snips, auto_lang_snips = get_file_snippets(file)
|
||||
-- store snippets to prevent parsing the same file more than once.
|
||||
cache.path_snippets[file] = {
|
||||
snippets = vim.deepcopy(lang_snips),
|
||||
autosnippets = vim.deepcopy(auto_lang_snips),
|
||||
add_opts = add_opts,
|
||||
fts = { [lang] = true },
|
||||
}
|
||||
if snip then
|
||||
if session.config.loaders_store_source then
|
||||
-- only know file, not line or line_end.
|
||||
snip._source = source.from_location(file)
|
||||
end
|
||||
|
||||
ls.add_snippets(
|
||||
lang,
|
||||
lang_snips,
|
||||
vim.tbl_extend("keep", {
|
||||
type = "snippets",
|
||||
-- again, include filetype, same reasoning as with augroup.
|
||||
key = string.format("__%s_snippets_%s", lang, file),
|
||||
refresh_notify = false,
|
||||
}, add_opts)
|
||||
)
|
||||
ls.add_snippets(
|
||||
lang,
|
||||
auto_lang_snips,
|
||||
vim.tbl_extend("keep", {
|
||||
type = "autosnippets",
|
||||
key = string.format("__%s_autosnippets_%s", lang, file),
|
||||
refresh_notify = false,
|
||||
}, add_opts)
|
||||
)
|
||||
log.info(
|
||||
"Adding %s snippets and %s autosnippets for filetype `%s` from %s",
|
||||
#lang_snips,
|
||||
#auto_lang_snips,
|
||||
lang,
|
||||
file
|
||||
)
|
||||
else
|
||||
log.error(
|
||||
"Trying to read snippets from file %s, but it does not exist.",
|
||||
lang,
|
||||
file
|
||||
)
|
||||
table.insert(snippets, snip)
|
||||
end
|
||||
end
|
||||
|
||||
ls.refresh_notify(lang)
|
||||
return snippets
|
||||
end
|
||||
|
||||
-- `refresh` to optionally delay refresh_notify.
|
||||
-- (it has to be called by the caller, for filetype!)
|
||||
-- opts may contain:
|
||||
-- `refresh_notify`: refresh snippets for filetype immediately, default false.
|
||||
-- `force_reload`: don't use cache when reloading, default false
|
||||
local function load_snippet_file(file, filetype, add_opts, opts)
|
||||
opts = opts or {}
|
||||
local refresh_notify =
|
||||
util.ternary(opts.refresh_notify ~= nil, opts.refresh_notify, false)
|
||||
local force_reload =
|
||||
util.ternary(opts.force_reload ~= nil, opts.force_reload, false)
|
||||
|
||||
if not Path.exists(file) then
|
||||
log.error(
|
||||
"Trying to read snippets from file %s, but it does not exist.",
|
||||
file
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
local file_snippets
|
||||
local cache = package_cache.path_snippets[file]
|
||||
if cache.snippets and not force_reload then
|
||||
file_snippets = vim.tbl_map(duplicate.duplicate_addable, cache.snippets)
|
||||
else
|
||||
file_snippets = get_file_snippets(file)
|
||||
|
||||
-- store snippets as-is (eg. don't copy), they will be copied when read
|
||||
-- from.
|
||||
package_cache.path_snippets[file].snippets = file_snippets
|
||||
end
|
||||
|
||||
ls.add_snippets(
|
||||
filetype,
|
||||
-- only load snippets matching the language set in `package.json`.
|
||||
file_snippets,
|
||||
vim.tbl_extend("keep", {
|
||||
-- include filetype, a file may contribute snippets to multiple
|
||||
-- filetypes, and we don't want to remove snippets for ft1 when
|
||||
-- adding those for ft2.
|
||||
key = string.format("__%s_snippets_%s", filetype, file),
|
||||
refresh_notify = refresh_notify,
|
||||
}, add_opts)
|
||||
)
|
||||
log.info("Adding %s snippets from %s", #file_snippets, file)
|
||||
end
|
||||
|
||||
--- Find all files+associated filetypes in a package.
|
||||
|
@ -150,8 +166,7 @@ end
|
|||
--- package.json)
|
||||
---@param filter function that filters filetypes, generate from in/exclude-list
|
||||
--- via loader_util.ft_filter.
|
||||
---@return table, string -> string[] (ft -> files).
|
||||
--- Paths are normalized.
|
||||
---@return table: string -> string[] (ft -> files)
|
||||
local function package_files(root, filter)
|
||||
local package = Path.join(root, "package.json")
|
||||
-- if root doesn't contain a package.json, or it contributes no snippets,
|
||||
|
@ -221,7 +236,9 @@ local function get_snippet_rtp()
|
|||
end, vim.api.nvim_get_runtime_file("package.json", true))
|
||||
end
|
||||
|
||||
-- sanitizes opts and returns ft -> files-map for `opts` (respects in/exclude).
|
||||
-- sanitizes opts and returns
|
||||
-- * ft -> files-map for `opts` (respects in/exclude).
|
||||
-- * files -> ft-map (need to look up which filetypes a file contributes).
|
||||
local function get_snippet_files(opts)
|
||||
local paths
|
||||
-- list of paths to crawl for loading (could be a table or a comma-separated-list)
|
||||
|
@ -232,6 +249,7 @@ local function get_snippet_files(opts)
|
|||
else
|
||||
paths = opts.paths
|
||||
end
|
||||
|
||||
paths = vim.tbl_map(Path.expand, paths) -- Expand before deduping, fake paths will become nil
|
||||
paths = vim.tbl_filter(function(v)
|
||||
return v
|
||||
|
@ -251,39 +269,65 @@ local function get_snippet_files(opts)
|
|||
return ft_paths
|
||||
end
|
||||
|
||||
-- initializes ft_paths for `file`, and stores the add_opts for the filetype-file combination.
|
||||
-- We can't just store add_opts for a single file, since via in/exclude, they
|
||||
-- may differ for a single file which contributes multiple snippet-filetypes.
|
||||
local function update_cache(cache, file, filetype, add_opts)
|
||||
local filecache = cache.path_snippets[file]
|
||||
if not filecache then
|
||||
filecache = {
|
||||
filetype_add_opts = {},
|
||||
filetypes = {},
|
||||
}
|
||||
cache.path_snippets[file] = filecache
|
||||
end
|
||||
|
||||
filecache.filetype_add_opts[filetype] = add_opts
|
||||
filecache.filetypes[filetype] = true
|
||||
end
|
||||
|
||||
local M = {}
|
||||
function M.load(opts)
|
||||
opts = opts or {}
|
||||
|
||||
-- applies in/exclude.
|
||||
local ft_files = get_snippet_files(opts)
|
||||
local add_opts = loader_util.add_opts(opts)
|
||||
|
||||
loader_util.extend_ft_paths(cache.ft_paths, ft_files)
|
||||
loader_util.extend_ft_paths(package_cache.ft_paths, ft_files)
|
||||
|
||||
log.info("Loading snippet:", vim.inspect(ft_files))
|
||||
for ft, files in pairs(ft_files) do
|
||||
load_snippet_files(ft, files, add_opts)
|
||||
for _, file in ipairs(files) do
|
||||
update_cache(package_cache, file, ft, add_opts)
|
||||
|
||||
-- `false`: don't refresh while adding.
|
||||
load_snippet_file(file, ft, add_opts, { refresh_notify = false })
|
||||
end
|
||||
ls.refresh_notify(ft)
|
||||
end
|
||||
end
|
||||
|
||||
function M._load_lazy_loaded_ft(ft)
|
||||
for _, load_call_paths in ipairs(cache.lazy_load_paths) do
|
||||
load_snippet_files(
|
||||
for _, file in ipairs(package_cache.lazy_load_paths[ft] or {}) do
|
||||
load_snippet_file(
|
||||
file,
|
||||
ft,
|
||||
load_call_paths[ft] or {},
|
||||
load_call_paths.add_opts
|
||||
package_cache.path_snippets[file].filetype_add_opts[ft],
|
||||
{ refresh_notify = false }
|
||||
)
|
||||
end
|
||||
ls.refresh_notify(ft)
|
||||
end
|
||||
|
||||
function M._load_lazy_loaded(bufnr)
|
||||
local fts = loader_util.get_load_fts(bufnr)
|
||||
|
||||
for _, ft in ipairs(fts) do
|
||||
if not cache.lazy_loaded_ft[ft] then
|
||||
if not package_cache.lazy_loaded_ft[ft] then
|
||||
M._load_lazy_loaded_ft(ft)
|
||||
log.info("Loading lazy-load-snippets for filetype `%s`", ft)
|
||||
cache.lazy_loaded_ft[ft] = true
|
||||
package_cache.lazy_loaded_ft[ft] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -291,56 +335,124 @@ end
|
|||
function M.lazy_load(opts)
|
||||
opts = opts or {}
|
||||
|
||||
-- get two maps, one mapping filetype->associated files, and another
|
||||
-- mapping files->default-filetypes.
|
||||
local ft_files = get_snippet_files(opts)
|
||||
local add_opts = loader_util.add_opts(opts)
|
||||
|
||||
loader_util.extend_ft_paths(cache.ft_paths, ft_files)
|
||||
loader_util.extend_ft_paths(package_cache.ft_paths, ft_files)
|
||||
|
||||
-- immediately load filetypes that have already been loaded.
|
||||
-- They will not be loaded otherwise.
|
||||
for ft, files in pairs(ft_files) do
|
||||
if cache.lazy_loaded_ft[ft] then
|
||||
-- instantly load snippets if they were already loaded...
|
||||
load_snippet_files(ft, files, add_opts)
|
||||
log.info(
|
||||
"Immediately loading lazy-load-snippets for already-active filetype %s from files:\n%s",
|
||||
ft,
|
||||
vim.inspect(files)
|
||||
)
|
||||
-- first register add_opts for all files, then iterate over files again
|
||||
-- if they are already loaded.
|
||||
|
||||
for _, file in ipairs(files) do
|
||||
update_cache(package_cache, file, ft, add_opts)
|
||||
end
|
||||
|
||||
if package_cache.lazy_loaded_ft[ft] then
|
||||
for _, file in ipairs(files) do
|
||||
-- instantly load snippets if they were already loaded...
|
||||
load_snippet_file(
|
||||
file,
|
||||
ft,
|
||||
add_opts,
|
||||
{ refresh_notify = false }
|
||||
)
|
||||
log.info(
|
||||
"Immediately loading lazy-load-snippets for already-active filetype %s from files:\n%s",
|
||||
ft,
|
||||
vim.inspect(files)
|
||||
)
|
||||
end
|
||||
ls.refresh_notify(ft)
|
||||
|
||||
-- don't load these files again.
|
||||
-- clearing while iterating is fine: https://www.lua.org/manual/5.1/manual.html#pdf-next
|
||||
ft_files[ft] = nil
|
||||
end
|
||||
end
|
||||
log.info("Registering lazy-load-snippets:\n%s", vim.inspect(ft_files))
|
||||
|
||||
ft_files.add_opts = add_opts
|
||||
table.insert(cache.lazy_load_paths, ft_files)
|
||||
loader_util.extend_ft_paths(package_cache.lazy_load_paths, ft_files)
|
||||
|
||||
-- load for current buffer on startup.
|
||||
M._load_lazy_loaded(vim.api.nvim_get_current_buf())
|
||||
end
|
||||
|
||||
function M.edit_snippet_files()
|
||||
loader_util.edit_snippet_files(cache.ft_paths)
|
||||
loader_util.edit_snippet_files(package_cache.ft_paths)
|
||||
end
|
||||
|
||||
-- Make sure filename is normalized.
|
||||
function M._reload_file(filename)
|
||||
local cached_data = cache.path_snippets[filename]
|
||||
if not cached_data then
|
||||
-- file is not loaded by this loader.
|
||||
return
|
||||
local function standalone_add(path, add_opts)
|
||||
local file_snippets = get_file_snippets(path)
|
||||
|
||||
ls.add_snippets(
|
||||
-- nil: provided snippets are a table mapping filetype->snippets.
|
||||
"all",
|
||||
file_snippets,
|
||||
vim.tbl_extend("keep", {
|
||||
key = string.format("__snippets_%s", path),
|
||||
}, add_opts)
|
||||
)
|
||||
end
|
||||
|
||||
function M.load_standalone(opts)
|
||||
opts = opts or {}
|
||||
local path = Path.expand(opts.path)
|
||||
local add_opts = loader_util.add_opts(opts)
|
||||
|
||||
-- register file for `all`-filetype in cache.
|
||||
if not standalone_cache.ft_paths.all then
|
||||
standalone_cache.ft_paths.all = {}
|
||||
end
|
||||
log.info("Re-loading snippets contributed by %s", filename)
|
||||
|
||||
cache.path_snippets[filename] = nil
|
||||
local add_opts = cached_data.add_opts
|
||||
-- record in cache, so edit_snippet_files can find it.
|
||||
-- Store under "all" for now, alternative: collect all filetypes the
|
||||
-- snippets contribute to.
|
||||
-- Since .code-snippets are mainly (?) project-local, that behaviour does
|
||||
-- not seem to bad.
|
||||
table.insert(standalone_cache.ft_paths.all, path)
|
||||
|
||||
-- reload file for all filetypes it occurs in.
|
||||
for ft, _ in pairs(cached_data.fts) do
|
||||
load_snippet_files(ft, { filename }, add_opts)
|
||||
-- only store add_opts, we don't need to remember filetypes and the like,
|
||||
-- and here the filename is enough to identify add_opts.
|
||||
standalone_cache.path_snippets[path] = add_opts
|
||||
|
||||
standalone_add(path, add_opts)
|
||||
end
|
||||
|
||||
-- filename is normalized
|
||||
function M._reload_file(filename)
|
||||
local package_cached_data = package_cache.path_snippets[filename]
|
||||
if package_cached_data then
|
||||
log.info("Re-loading snippets contributed by %s", filename)
|
||||
|
||||
-- reload file for all filetypes it occurs in.
|
||||
-- only the first call actually needs to force-reload, all other can
|
||||
-- just use its snippets.
|
||||
local force_reload = true
|
||||
for ft, _ in pairs(package_cached_data.filetypes) do
|
||||
load_snippet_file(
|
||||
filename,
|
||||
ft,
|
||||
package_cached_data.filetype_add_opts[ft],
|
||||
{ force_reload = force_reload }
|
||||
)
|
||||
-- only force-reload once, then reuse updated snippets.
|
||||
force_reload = false
|
||||
end
|
||||
|
||||
ls.clean_invalidated({ inv_limit = 100 })
|
||||
end
|
||||
|
||||
local standalone_cached_data = standalone_cache.path_snippets[filename]
|
||||
if standalone_cached_data then
|
||||
log.info("Re-loading snippets contributed by %s", filename)
|
||||
local add_opts = standalone_cached_data
|
||||
|
||||
standalone_add(filename, add_opts)
|
||||
ls.clean_invalidated({ inv_limit = 100 })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
local Cache = require("luasnip.loaders._caches")
|
||||
local util = require("luasnip.util.util")
|
||||
local loader_util = require("luasnip.loaders.util")
|
||||
local Path = require("luasnip.util.path")
|
||||
|
||||
local M = {}
|
||||
|
||||
-- used to map cache-name to name passed to format.
|
||||
local clean_name = {
|
||||
vscode_packages = "vscode",
|
||||
vscode_standalone = "vscode-standalone",
|
||||
snipmate = "snipmate",
|
||||
lua = "lua",
|
||||
}
|
||||
local function default_format(path, _)
|
||||
path = path:gsub(
|
||||
vim.pesc(vim.fn.stdpath("data") .. "/site/pack/packer/start"),
|
||||
|
@ -38,7 +46,7 @@ function M.edit_snippet_files(opts)
|
|||
opts = opts or {}
|
||||
local format = opts.format or default_format
|
||||
local edit = opts.edit or default_edit
|
||||
local extend = opts.extend or function()
|
||||
local extend = opts.extend or function(_, _)
|
||||
return {}
|
||||
end
|
||||
|
||||
|
@ -48,9 +56,14 @@ function M.edit_snippet_files(opts)
|
|||
local items = {}
|
||||
|
||||
-- concat files from all loaders for the selected filetype ft.
|
||||
for _, cache_name in ipairs({ "vscode", "snipmate", "lua" }) do
|
||||
for _, cache_name in ipairs({
|
||||
"vscode_packages",
|
||||
"vscode_standalone",
|
||||
"snipmate",
|
||||
"lua",
|
||||
}) do
|
||||
for _, path in ipairs(Cache[cache_name].ft_paths[ft] or {}) do
|
||||
local fmt_name = format(path, cache_name)
|
||||
local fmt_name = format(path, clean_name[cache_name])
|
||||
if fmt_name then
|
||||
table.insert(ft_paths, path)
|
||||
table.insert(items, fmt_name)
|
||||
|
@ -86,8 +99,16 @@ function M.edit_snippet_files(opts)
|
|||
|
||||
local ft_filter = opts.ft_filter or util.yes
|
||||
|
||||
local all_fts = {}
|
||||
vim.list_extend(all_fts, util.get_snippet_filetypes())
|
||||
vim.list_extend(
|
||||
all_fts,
|
||||
loader_util.get_load_fts(vim.api.nvim_get_current_buf())
|
||||
)
|
||||
all_fts = util.deduplicate(all_fts)
|
||||
|
||||
local filtered_fts = {}
|
||||
for _, ft in ipairs(util.get_snippet_filetypes()) do
|
||||
for _, ft in ipairs(all_fts) do
|
||||
if ft_filter(ft) then
|
||||
table.insert(filtered_fts, ft)
|
||||
end
|
||||
|
|
70
lua/luasnip/nodes/duplicate.lua
Normal file
70
lua/luasnip/nodes/duplicate.lua
Normal file
|
@ -0,0 +1,70 @@
|
|||
local snip_mod = require("luasnip.nodes.snippet")
|
||||
|
||||
local M = {}
|
||||
|
||||
local DupExpandable = {}
|
||||
|
||||
-- just pass these through to _expandable.
|
||||
function DupExpandable:get_docstring()
|
||||
return self._expandable:get_docstring()
|
||||
end
|
||||
function DupExpandable:copy()
|
||||
local copy = self._expandable:copy()
|
||||
copy.id = self.id
|
||||
|
||||
return copy
|
||||
end
|
||||
|
||||
-- this is modified in `self:invalidate` _and_ needs to be called on _expandable.
|
||||
function DupExpandable:matches(...)
|
||||
-- use snippet-module matches, self._expandable might have had its match
|
||||
-- overwritten by invalidate.
|
||||
-- (if there are more issues with this, consider some other mechanism for
|
||||
-- invalidating)
|
||||
return snip_mod.Snippet.matches(self._expandable, ...)
|
||||
end
|
||||
|
||||
-- invalidate has to be called on this snippet itself.
|
||||
function DupExpandable:invalidate()
|
||||
snip_mod.Snippet.invalidate(self)
|
||||
end
|
||||
|
||||
local dup_mt = {
|
||||
-- index DupExpandable for own functions, and then the expandable stored in
|
||||
-- self/t.
|
||||
__index = function(t, k)
|
||||
if DupExpandable[k] then
|
||||
return DupExpandable[k]
|
||||
end
|
||||
|
||||
return t._expandable[k]
|
||||
end,
|
||||
}
|
||||
|
||||
function M.duplicate_expandable(expandable)
|
||||
return setmetatable({
|
||||
_expandable = expandable,
|
||||
-- copy these!
|
||||
-- if `expandable` is invalidated, we don't necessarily want this
|
||||
-- expandable to be invalidated as well.
|
||||
hidden = expandable.hidden,
|
||||
invalidated = expandable.invalidated,
|
||||
}, dup_mt)
|
||||
end
|
||||
|
||||
local DupAddable = {}
|
||||
|
||||
function DupAddable:retrieve_all()
|
||||
return vim.tbl_map(M.duplicate_expandable, self.addable:retrieve_all())
|
||||
end
|
||||
local DupAddable_mt = {
|
||||
__index = DupAddable,
|
||||
}
|
||||
|
||||
function M.duplicate_addable(addable)
|
||||
return setmetatable({
|
||||
addable = addable,
|
||||
}, DupAddable_mt)
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,5 +1,6 @@
|
|||
local snip_mod = require("luasnip.nodes.snippet")
|
||||
local node_util = require("luasnip.nodes.util")
|
||||
local extend_decorator = require("luasnip.util.extend_decorator")
|
||||
|
||||
local VirtualSnippet = {}
|
||||
local VirtualSnippet_mt = { __index = VirtualSnippet }
|
||||
|
@ -8,7 +9,10 @@ function VirtualSnippet:get_docstring()
|
|||
return self.snippet:get_docstring()
|
||||
end
|
||||
function VirtualSnippet:copy()
|
||||
return self.snippet:copy()
|
||||
local copy = self.snippet:copy()
|
||||
copy.id = self.id
|
||||
|
||||
return copy
|
||||
end
|
||||
|
||||
-- VirtualSnippet has all the fields for executing these methods.
|
||||
|
@ -38,25 +42,13 @@ function MultiSnippet:retrieve_all()
|
|||
return self.v_snips
|
||||
end
|
||||
|
||||
local function new_multisnippet(contexts, nodes, opts)
|
||||
local function multisnippet_from_snippet_obj(contexts, snippet, snippet_opts)
|
||||
assert(
|
||||
type(contexts) == "table",
|
||||
"multisnippet: expected contexts to be a table."
|
||||
)
|
||||
opts = opts or {}
|
||||
local common_snip_opts = opts.common_opts or {}
|
||||
|
||||
local common_context = node_util.wrap_context(contexts.common) or {}
|
||||
|
||||
-- create snippet without `context`-fields!
|
||||
-- compare to `S` (aka `s`, the default snippet-constructor) in
|
||||
-- `nodes/snippet.lua`.
|
||||
local snippet = snip_mod._S(
|
||||
snip_mod.init_snippet_opts(common_snip_opts),
|
||||
nodes,
|
||||
common_snip_opts
|
||||
)
|
||||
|
||||
local v_snips = {}
|
||||
for _, context in ipairs(contexts) do
|
||||
local complete_context = vim.tbl_extend(
|
||||
|
@ -66,7 +58,7 @@ local function new_multisnippet(contexts, nodes, opts)
|
|||
)
|
||||
table.insert(
|
||||
v_snips,
|
||||
new_virtual_snippet(complete_context, snippet, common_snip_opts)
|
||||
new_virtual_snippet(complete_context, snippet, snippet_opts)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -79,6 +71,47 @@ local function new_multisnippet(contexts, nodes, opts)
|
|||
return o
|
||||
end
|
||||
|
||||
local function multisnippet_from_nodes(contexts, nodes, opts)
|
||||
opts = opts or {}
|
||||
local common_snip_opts = opts.common_opts or {}
|
||||
|
||||
-- create snippet without `context`-fields!
|
||||
-- compare to `S` (aka `s`, the default snippet-constructor) in
|
||||
-- `nodes/snippet.lua`.
|
||||
return multisnippet_from_snippet_obj(
|
||||
contexts,
|
||||
snip_mod._S(
|
||||
snip_mod.init_snippet_opts(common_snip_opts),
|
||||
nodes,
|
||||
common_snip_opts
|
||||
),
|
||||
common_snip_opts
|
||||
)
|
||||
end
|
||||
|
||||
local function extend_multisnippet_contexts(passed_arg, extend_arg)
|
||||
-- extend passed arg with contexts passed in extend-call
|
||||
vim.list_extend(passed_arg, extend_arg)
|
||||
|
||||
-- extend ("keep") valid keyword-arguments.
|
||||
passed_arg.common = vim.tbl_deep_extend(
|
||||
"keep",
|
||||
node_util.wrap_context(passed_arg.common) or {},
|
||||
node_util.wrap_context(extend_arg.common) or {}
|
||||
)
|
||||
|
||||
return passed_arg
|
||||
end
|
||||
extend_decorator.register(
|
||||
multisnippet_from_nodes,
|
||||
-- first arg needs special handling (extend list of contexts (index i
|
||||
-- becomes i+#passed_arg, not i again))
|
||||
{ arg_indx = 1, extend = extend_multisnippet_contexts },
|
||||
-- opts can just be `vim.tbl_extend`ed.
|
||||
{ arg_indx = 3 }
|
||||
)
|
||||
|
||||
return {
|
||||
new_multisnippet = new_multisnippet,
|
||||
new_multisnippet = multisnippet_from_nodes,
|
||||
_raw_ms = multisnippet_from_snippet_obj,
|
||||
}
|
||||
|
|
|
@ -189,6 +189,9 @@ local function init_snippet_context(context, opts)
|
|||
or context.snippetType == "snippet" and "snippets"
|
||||
or nil
|
||||
|
||||
-- may be nil.
|
||||
effective_context.filetype = context.filetype
|
||||
|
||||
-- maybe do this in a better way when we have more parameters, but this is
|
||||
-- fine for now:
|
||||
|
||||
|
|
|
@ -47,12 +47,16 @@ end
|
|||
|
||||
-- some values of the snippet are nil by default, list them here so snippets
|
||||
-- aren't instantiated because of them.
|
||||
local license_to_nil = { priority = true, snippetType = true, _source = true }
|
||||
local license_to_nil =
|
||||
{ priority = true, snippetType = true, _source = true, filetype = true }
|
||||
|
||||
-- context and opts are (almost) the same objects as in s(contex, nodes, opts), snippet is a string representing the snippet.
|
||||
-- opts can aditionally contain the key `parse_fn`, which will be used to parse
|
||||
-- the snippet. This is useful, since snipmate-snippets are parsed with a
|
||||
-- function than regular lsp-snippets.
|
||||
-- context can be nil, in that case the resulting object can't be inserted into
|
||||
-- the snippet-tables, but may be used after expansion (i.e. returned from
|
||||
-- snippet:copy)
|
||||
local function new(context, snippet, opts)
|
||||
opts = opts or {}
|
||||
|
||||
|
@ -66,7 +70,12 @@ local function new(context, snippet, opts)
|
|||
local sp = vim.tbl_extend(
|
||||
"error",
|
||||
{},
|
||||
snip_mod.init_snippet_context(node_util.wrap_context(context), opts),
|
||||
context
|
||||
and snip_mod.init_snippet_context(
|
||||
node_util.wrap_context(context),
|
||||
opts
|
||||
)
|
||||
or {},
|
||||
snip_mod.init_snippet_opts(opts),
|
||||
node_util.init_node_opts(opts)
|
||||
)
|
||||
|
@ -99,7 +108,10 @@ local function new(context, snippet, opts)
|
|||
-- when the metatable has been changed. Therefore: set copy in each instance
|
||||
-- of snippetProxy.
|
||||
function sp:copy()
|
||||
return self._snippet:copy()
|
||||
local copy = self._snippet:copy()
|
||||
copy.id = self.id
|
||||
|
||||
return copy
|
||||
end
|
||||
|
||||
return sp
|
||||
|
|
|
@ -229,11 +229,11 @@ function M.clean_invalidated(opts)
|
|||
M.invalidated_count = 0
|
||||
end
|
||||
|
||||
local function invalidate_snippets(snippets_by_ft)
|
||||
for _, ft_snippets in pairs(snippets_by_ft) do
|
||||
for _, addable in ipairs(ft_snippets) do
|
||||
for _, snip in ipairs(addable:retrieve_all()) do
|
||||
snip:invalidate()
|
||||
local function invalidate_addables(addables_by_ft)
|
||||
for _, addables in pairs(addables_by_ft) do
|
||||
for _, addable in ipairs(addables) do
|
||||
for _, expandable in ipairs(addable:retrieve_all()) do
|
||||
expandable:invalidate()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -247,26 +247,27 @@ function M.add_snippets(snippets, opts)
|
|||
for ft, ft_snippets in pairs(snippets) do
|
||||
for _, addable in ipairs(ft_snippets) do
|
||||
for _, snip in ipairs(addable:retrieve_all()) do
|
||||
snip.priority = opts.override_priority
|
||||
local snip_prio = opts.override_priority
|
||||
or (snip.priority and snip.priority)
|
||||
or opts.default_priority
|
||||
or 1000
|
||||
|
||||
-- if snippetType undefined by snippet, take default value from opts
|
||||
snip.snippetType = snip.snippetType ~= nil and snip.snippetType
|
||||
local snip_type = snip.snippetType ~= nil and snip.snippetType
|
||||
or opts.type
|
||||
assert(
|
||||
snip.snippetType == "autosnippets"
|
||||
or snip.snippetType == "snippets",
|
||||
"snipptType must be either 'autosnippets' or 'snippets'"
|
||||
snip_type == "autosnippets" or snip_type == "snippets",
|
||||
"snippetType must be either 'autosnippets' or 'snippets'"
|
||||
)
|
||||
|
||||
local snip_ft = snip.filetype or ft
|
||||
|
||||
snip.id = current_id
|
||||
current_id = current_id + 1
|
||||
|
||||
-- do the insertion
|
||||
table.insert(by_prio[snip.snippetType][snip.priority][ft], snip)
|
||||
table.insert(by_ft[snip.snippetType][ft], snip)
|
||||
table.insert(by_prio[snip_type][snip_prio][snip_ft], snip)
|
||||
table.insert(by_ft[snip_type][snip_ft], snip)
|
||||
by_id[snip.id] = snip
|
||||
|
||||
-- set source if it was passed, and remove from snippet.
|
||||
|
@ -280,7 +281,7 @@ function M.add_snippets(snippets, opts)
|
|||
|
||||
if opts.key then
|
||||
if by_key[opts.key] then
|
||||
invalidate_snippets(by_key[opts.key])
|
||||
invalidate_addables(by_key[opts.key])
|
||||
end
|
||||
by_key[opts.key] = snippets
|
||||
end
|
||||
|
|
|
@ -31,6 +31,12 @@
|
|||
"all"
|
||||
],
|
||||
"path": "snippets/all.jsonc"
|
||||
},
|
||||
{
|
||||
"language": [
|
||||
"all"
|
||||
],
|
||||
"path": "./snippets/all.code-snippets"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
10
tests/data/vscode-snippets/snippets/all.code-snippets
Normal file
10
tests/data/vscode-snippets/snippets/all.code-snippets
Normal file
|
@ -0,0 +1,10 @@
|
|||
// uh oh, a comment.
|
||||
{
|
||||
"a": {
|
||||
"prefix": "codesnippets",
|
||||
"body": [
|
||||
"code-snippets!!!"
|
||||
]
|
||||
}
|
||||
/* oh no a block comment */
|
||||
}
|
|
@ -15,6 +15,13 @@
|
|||
"luasnip": {
|
||||
"priority": 2000
|
||||
}
|
||||
},
|
||||
"c": {
|
||||
"prefix": "cc",
|
||||
"body": [
|
||||
"3"
|
||||
],
|
||||
"scope": "cpp,c"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
36
tests/data/vscode-standalone.code-snippets
Normal file
36
tests/data/vscode-standalone.code-snippets
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"snip1": {
|
||||
"prefix": "vscode_lua1",
|
||||
"body": [
|
||||
"vscode$1lualualua"
|
||||
],
|
||||
"scope": "lua"
|
||||
},
|
||||
"snip1": {
|
||||
"prefix": "all1",
|
||||
"body": [
|
||||
"expands? jumps? $1 $2 !"
|
||||
],
|
||||
"scope": "all"
|
||||
},
|
||||
"snip2": {
|
||||
"prefix": "all2",
|
||||
"body": [
|
||||
"multi $1",
|
||||
"line $2",
|
||||
"#not removed??",
|
||||
"snippet$0"
|
||||
],
|
||||
// empty scope should be all
|
||||
},
|
||||
"snip2": {
|
||||
"prefix": "vscode_lua2",
|
||||
"body": [
|
||||
"vscode$1lualualua"
|
||||
],
|
||||
"luasnip": {
|
||||
"autotrigger": true
|
||||
},
|
||||
"scope": "all"
|
||||
}
|
||||
}
|
|
@ -154,6 +154,15 @@ M.loaders = {
|
|||
)
|
||||
)
|
||||
end,
|
||||
["vscode(standalone)"] = function()
|
||||
exec_lua(
|
||||
string.format(
|
||||
[[require("luasnip.loaders.from_vscode").load_standalone({path="%s"})]],
|
||||
os.getenv("LUASNIP_SOURCE")
|
||||
.. "/tests/data/vscode-standalone.code-snippets"
|
||||
)
|
||||
)
|
||||
end,
|
||||
|
||||
["snipmate(rtp)"] = function()
|
||||
exec(
|
||||
|
|
|
@ -309,4 +309,30 @@ describe("add_snippets", function()
|
|||
{2:-- INSERT --} |]],
|
||||
})
|
||||
end)
|
||||
|
||||
it("snippets' filetype overrides add_snippets-filetype", function()
|
||||
exec_lua([[
|
||||
ls.add_snippets("c", {
|
||||
s({trig = "in_lua", filetype = "lua"}, {t"expanded in lua"})
|
||||
})
|
||||
]])
|
||||
exec("set ft=c")
|
||||
feed("iin_lua")
|
||||
exec_lua("ls.expand()")
|
||||
screen:expect({
|
||||
grid = [[
|
||||
in_lua^ |
|
||||
{0:~ }|
|
||||
{2:-- INSERT --} |]],
|
||||
})
|
||||
exec("set ft=lua")
|
||||
feed("<Cr>in_lua")
|
||||
exec_lua("ls.expand()")
|
||||
screen:expect({
|
||||
grid = [[
|
||||
in_lua |
|
||||
expanded in lua^ |
|
||||
{2:-- INSERT --} |]],
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -388,6 +388,12 @@ describe("loaders:", function()
|
|||
"/tests/data/vscode-snippets/snippets/all.json",
|
||||
"<Esc>4jwlcereplaces<Esc>:w<Cr><C-O>ccall1"
|
||||
)
|
||||
reload_test(
|
||||
"vscode-standalone-reload works",
|
||||
ls_helpers.loaders["vscode(standalone)"],
|
||||
"/tests/data/vscode-standalone.code-snippets",
|
||||
"<Esc>11jwlcereplaces<Esc>:w<Cr><C-O>ccall1"
|
||||
)
|
||||
|
||||
reload_test(
|
||||
"lua-reload works",
|
||||
|
@ -484,4 +490,70 @@ describe("loaders:", function()
|
|||
"/tests/symlinked_data/lua-snippets/luasnippets/all.lua",
|
||||
"<Esc>jfecereplaces<Esc>:w<Cr><C-O>ccall1"
|
||||
)
|
||||
|
||||
it("Can load files with `code-snippets`-extension.", function()
|
||||
ls_helpers.loaders["vscode(rtp)"]()
|
||||
|
||||
feed("icodesnippets")
|
||||
exec_lua("ls.expand()")
|
||||
screen:expect({
|
||||
grid = [[
|
||||
code-snippets!!!^ |
|
||||
{0:~ }|
|
||||
{0:~ }|
|
||||
{0:~ }|
|
||||
{2:-- INSERT --} |]],
|
||||
})
|
||||
end)
|
||||
|
||||
it("Respects `scope` (vscode)", function()
|
||||
ls_helpers.loaders["vscode(rtp)"]()
|
||||
|
||||
feed("icc")
|
||||
exec_lua("ls.expand()")
|
||||
screen:expect({
|
||||
grid = [[
|
||||
cc^ |
|
||||
{0:~ }|
|
||||
{0:~ }|
|
||||
{0:~ }|
|
||||
{2:-- INSERT --} |]],
|
||||
})
|
||||
|
||||
exec("set ft=c")
|
||||
feed("<Cr>cc")
|
||||
exec_lua("ls.expand()")
|
||||
screen:expect({
|
||||
grid = [[
|
||||
cc |
|
||||
3^ |
|
||||
{0:~ }|
|
||||
{0:~ }|
|
||||
{2:-- INSERT --} |]],
|
||||
})
|
||||
-- check if invalidation affects the duplicated snippet.
|
||||
exec_lua([[ls.get_snippets("c")[1]:invalidate()]])
|
||||
feed("<Cr>cc")
|
||||
exec_lua("ls.expand()")
|
||||
screen:expect({
|
||||
grid = [[
|
||||
cc |
|
||||
3 |
|
||||
cc^ |
|
||||
{0:~ }|
|
||||
{2:-- INSERT --} |]],
|
||||
})
|
||||
|
||||
exec("set ft=cpp")
|
||||
feed("<Cr>cc")
|
||||
exec_lua("ls.expand()")
|
||||
screen:expect({
|
||||
grid = [[
|
||||
cc |
|
||||
3 |
|
||||
cc |
|
||||
3^ |
|
||||
{2:-- INSERT --} |]],
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -147,4 +147,36 @@ describe("multisnippets", function()
|
|||
assert(ls.__did_expand)
|
||||
]])
|
||||
end)
|
||||
|
||||
it("work with extend_decorator", function()
|
||||
ls_helpers.session_setup_luasnip({
|
||||
setup_extend = { enable_autosnippets = true },
|
||||
})
|
||||
|
||||
exec_lua([[
|
||||
-- contexts without trigger get "asdf", add one context which has
|
||||
-- the default-trigger and is an autosnippet.
|
||||
local auto_multisnippet = ls.extend_decorator.apply(ls.multi_snippet, {common = "asdf", {snippetType = "autosnippet"}})
|
||||
|
||||
ls.add_snippets("all", {
|
||||
auto_multisnippet({"bsdf"}, {t"csdf"})
|
||||
}, {key = "asdf"})
|
||||
]])
|
||||
feed("iasdf")
|
||||
screen:expect({
|
||||
grid = [[
|
||||
csdf^ |
|
||||
{0:~ }|
|
||||
{2:-- INSERT --} |]],
|
||||
})
|
||||
|
||||
feed("<Cr>bsdf")
|
||||
exec_lua("ls.expand()")
|
||||
screen:expect({
|
||||
grid = [[
|
||||
csdf |
|
||||
csdf^ |
|
||||
{2:-- INSERT --} |]],
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -29,6 +29,7 @@ describe("snippetProxy", function()
|
|||
".wordTrig",
|
||||
".regTrig",
|
||||
".dscr",
|
||||
".filetype",
|
||||
".name",
|
||||
".callbacks",
|
||||
".condition",
|
||||
|
|
Loading…
Reference in a new issue