Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement snippet priority #57

Merged
merged 3 commits into from
Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions doc/snippy.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ CONTENTS *snippy-contents*
INTRODUCTION.........................|snippy-introduction|
USAGE................................|snippy-usage|
FUNCTIONS............................|snippy-functions|
OPTIONS..............................|snippy-options|
SETUP OPTIONS........................|snippy-setup-options|
COMMANDS.............................|snippy-commands|
LICENSE..............................|snippy-license|


==============================================================================
INTRODUCTION *snippy-introduction*

Snippy is a lua-based snippets plugin for Neovim.
Snippy is a snippets plugin for Neovim written in Lua.


==============================================================================
Expand All @@ -25,9 +25,9 @@ USAGE *snippy-usage*
*snippy-usage-setup*

Snippy includes an optional |snippy.setup()| function for you to quickly
configure it according to your needs. See the |snippy-options| section for
information on the available options. Below is an example showing most of the
available options:
configure it according to your needs. See the |snippy-setup-options| section
for information on the available options. Below is an example showing most of
the available options:
>
require('snippy').setup({
snippet_dirs = '~/snippets',
Expand Down Expand Up @@ -93,7 +93,7 @@ be used later by the `$VISUAL` or `$TM_SELECTED_TEXT` variables:
When mapping from Lua using |nvim_set_keymap()|, you can use
|snippy.can_expand()| and |snippy.can_jump()| functions. The Vimscript
functions above are just convenience wrappings around those. You can also map
keys by using the `mappings` setup option (see |snippy-options|).
keys by using the `mappings` setup option (see |snippy-setup-options|).

*snippy-usage-snippets*

Expand Down Expand Up @@ -148,12 +148,26 @@ This could also be achieved with an |autocmd|. Indenting with spaces is a
Snippy feature and might not be compatible with other snippet plugins like
SnipMate.

Note: Snippy follows the |runtimepath| order when loading snippets, which
*snippy-usage-priority*

Snippy follows the |runtimepath| order when loading snippets, which
means snippets defined in folders coming later in the list will always
override those loaded before. So, if you put your custom snippets in
`after/snippets/`, they should override any snippets provided by plugins in
case of name conflict.

Another way to determine which snippets should be loaded first is to use the
`priority` directive, currently only possible in `.snippets` files. The
default priority is 0, so you can set your own snippets' priority to a higher
value, like 100, for them to override lower priority snippets. The priority
directive affects all snippets listed after it:
>
priority 100
snippet trigger1
...
snippet trigger2
...
<
*snippy-usage-scopes*

Scopes are the term used in Snippy to refer to a group of snippets that should
Expand Down Expand Up @@ -234,7 +248,7 @@ TextMate/LSP syntax.
FUNCTIONS *snippy-functions*

snippy.setup({options}) *snippy.setup()*
Set initial configuration for the plugin. See also |snippy-options|.
Set initial configuration for the plugin. See also |snippy-setup-options|.

snippy.setup_buffer({bufnr}, {options}) *snippy.setup_buffer()*
Set configuration for the current buffer. See also
Expand Down Expand Up @@ -308,9 +322,9 @@ snippy#can_expand_or_advance() *snippy#can_expand_or_advance()*


==============================================================================
OPTIONS *snippy-options*
SETUP OPTIONS *snippy-setup-options*

This plugin can be configured by passing a dictionary to the lua function
This plugin can be configured by passing a dictionary to the Lua function
`snippy.setup(...)`. The following options are supported:

snippet_dirs ~
Expand Down
51 changes: 40 additions & 11 deletions lua/snippy/reader/snipmate.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ end
local function read_snippets_file(snippets_file)
local snips = {}
local extends = {}
local priority = 0
local file = io.open(snippets_file)
local lines = vim.split(file:read('*a'), '\n')
if lines[#lines] == '' then
Expand All @@ -57,7 +58,7 @@ local function read_snippets_file(snippets_file)
local option = description and parse_options(prefix, line) or {}
if option.auto_trigger and not shared.config.enable_auto then
local msg = [[[Snippy] Warning: you seem to have autotriggered snippets,]]
.. [[ but the autotrigger feature isn\'t enabled in your config.]]
.. [[ but the autotrigger feature isn't enabled in your config.]]
.. [[ See :help snippy-snippet-options for details.]]
api.nvim_echo({ { msg, 'WarningMsg' } }, true, {})
end
Expand All @@ -83,13 +84,16 @@ local function read_snippets_file(snippets_file)
break
end
end
snips[prefix] = {
kind = 'snipmate',
prefix = prefix,
description = description,
option = option,
body = body,
}
if not snips[prefix] or snips[prefix].priority <= priority then
snips[prefix] = {
kind = 'snipmate',
prefix = prefix,
priority = priority,
description = description,
option = option,
body = body,
}
end
end

while i <= #lines do
Expand All @@ -100,11 +104,18 @@ local function read_snippets_file(snippets_file)
local scopes = vim.split(vim.trim(line:sub(8)), '%s+')
vim.list_extend(extends, scopes)
i = i + 1
elseif line:sub(1, 8) == 'priority' then
local prio = vim.trim(line:sub(9))
if not prio or not prio:match('-%d+') or not prio:match('+?%d+') then
error(string.format('Invalid priority in file %s, at line %s: %s', snippets_file, i, prio))
end
priority = tonumber(prio)
i = i + 1
elseif line:sub(1, 1) == '#' or vim.trim(line) == '' then
-- Skip empty lines or comments
i = i + 1
else
error(string.format('Invalid line in snippets file %s: %s', snippets_file, line))
error(string.format('Unrecognized syntax in snippets file %s, at line %s: %s', snippets_file, i, line))
end
end
return snips, extends
Expand All @@ -128,6 +139,8 @@ local function read_snippet_file(snippet_file, scope)
kind = 'snipmate',
prefix = prefix,
description = description,
-- Priority for .snippet is always 0
priority = 0,
body = body,
},
}
Expand Down Expand Up @@ -156,6 +169,22 @@ local function list_files(ftype)
return all
end

local function merge_snippets(current, added)
local result = vim.deepcopy(current)
for key, val in pairs(added) do
if current[key] then
local cur_snip = current[key]
local new_snip = added[key]
if new_snip.priority >= cur_snip.priority then
result[key] = val
end
else
result[key] = val
end
end
return result
end

local function load_scope(scope, stack)
local snips = {}
local extends = {}
Expand All @@ -168,7 +197,7 @@ local function load_scope(scope, stack)
elseif file:match('.snippet$') then
result = read_snippet_file(file, scope)
end
snips = vim.tbl_extend('force', snips, result)
snips = merge_snippets(snips, result)
end
for _, extended in ipairs(extends) do
if vim.tbl_contains(stack, extended) then
Expand All @@ -180,7 +209,7 @@ local function load_scope(scope, stack)
)
end
local result = load_scope(extended, vim.tbl_flatten({ stack, scope }))
snips = vim.tbl_extend('keep', snips, result)
snips = merge_snippets(snips, result)
end
return snips
end
Expand Down
21 changes: 18 additions & 3 deletions test/unit/reader_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,20 @@ describe('Snippet reader', function()
snippy.setup({ snippet_dirs = './test/snippets/' })
vim.cmd('set filetype=')
local snips = {
test1 = { kind = 'snipmate', prefix = 'test1', option = {}, body = { 'This is the first test.' } },
test2 = { kind = 'snipmate', prefix = 'test2', option = {}, body = { 'This is the second test.' } },
test1 = {
kind = 'snipmate',
prefix = 'test1',
option = {},
priority = 0,
body = { 'This is the first test.' },
},
test2 = {
kind = 'snipmate',
prefix = 'test2',
option = {},
priority = 0,
body = { 'This is the second test.' },
},
}
assert.is_truthy(require('snippy.shared').config.snippet_dirs)
assert.is_not.same({}, require('snippy.reader.snipmate').list_available_scopes())
Expand All @@ -24,6 +36,7 @@ describe('Snippet reader', function()
no_description = {
kind = 'snipmate',
prefix = 'no_description',
priority = 0,
body = {
'This is a *.snippet file with no description.',
},
Expand All @@ -32,6 +45,7 @@ describe('Snippet reader', function()
kind = 'snipmate',
prefix = 'trigger',
description = 'description',
priority = 0,
body = {
'This is a *.snippet file with a description.',
},
Expand All @@ -50,6 +64,7 @@ describe('Snippet reader', function()
prefix = 'trigger',
option = {},
description = 'description',
priority = 0,
body = {
'This is indented with two spaces.',
'\tThis is indented with four spaces.',
Expand All @@ -63,7 +78,7 @@ describe('Snippet reader', function()
end)

it('can read snippets with otions', function()
snippy.setup({ snippet_dirs = './test/snippets/' })
snippy.setup({ snippet_dirs = './test/snippets/', enable_auto = true })
vim.cmd('set filetype=java')
assert.is_truthy(require('snippy.shared').config.snippet_dirs)
assert.is_not.same({}, require('snippy.reader.snipmate').list_available_scopes())
Expand Down