Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/norcalli/snippets.nvim
https://github.com/norcalli/snippets.nvim
Last synced: 3 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/norcalli/snippets.nvim
- Owner: norcalli
- License: gpl-3.0
- Created: 2020-07-18T17:34:57.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2022-09-13T19:07:08.000Z (about 2 years ago)
- Last Synced: 2024-07-31T20:44:00.721Z (3 months ago)
- Language: Lua
- Homepage:
- Size: 146 KB
- Stars: 277
- Watchers: 8
- Forks: 13
- Open Issues: 17
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-neovim - norcalli/snippets.nvim - Snippets in Lua. (Snippet / PHP)
README
# snippets.nvim
Check the [Wiki](https://github.com/norcalli/snippets.nvim/wiki) for examples and contribute your own!
# Installation
`Plug norcalli/snippets.nvim`
# Usage:
```vim
lua require'snippets'.use_suggested_mappings()" This variant will set up the mappings only for the *CURRENT* buffer.
lua require'snippets'.use_suggested_mappings(true)" There are only two keybindings specified by the suggested keymappings, which is and
" They are exactly equivalent to:" will either expand the current snippet at the word or try to jump to
" the next position for the snippet.
inoremap lua return require'snippets'.expand_or_advance(1)" will jump backwards to the previous field.
" If you jump before the first field, it will cancel the snippet.
inoremap lua return require'snippets'.advance_snippet(-1)```
The rest of the README is in Lua **NOT** VIM SCRIPT.
```lua
require'snippets'.snippets = {
-- The _global dictionary acts as a global fallback.
-- If a key is not found for the specific filetype, then
-- it will be lookup up in the _global dictionary.
_global = {
-- Insert a basic snippet, which is a string.
todo = "TODO(ashkan): ";uname = function() return vim.loop.os_uname().sysname end;
date = os.date;-- Evaluate at the time of the snippet expansion and insert it. You
-- can put arbitrary lua functions inside of the =... block as a
-- dynamic placeholder. In this case, for an anonymous variable
-- which doesn't take user input and is evaluated at the start.
epoch = "${=os.time()}";
-- Equivalent to above.
epoch = function() return os.time() end;-- Use the expansion to read the username dynamically.
note = [[NOTE(${=io.popen("id -un"):read"*l"}): ]];-- Do the same as above, but by using $1, we can make it user input.
-- That means that the user will be prompted at the field during expansion.
-- You can *EITHER* specify an expression as a placeholder for a variable
-- or a literal string/snippet using `${var:...}`, but not both.
note = [[NOTE(${1=io.popen("id -un"):read"*l"}): ]];
};
lua = {
-- Snippets can be used inside of placeholders, but the variables used in
-- the placeholder *must* be used outside of the placeholder. This could
-- potentially change in the future if someone convinces me it's a good
-- idea to support it. (it was a deliberate choice)
req = [[local ${2:$1} = require '$1']];-- A snippet with a placeholder using :... and multiple variables.
["for"] = "for ${1:i}, ${2:v} in ipairs(${3:t}) do\n$0\nend";
-- This is equivalent to above, but looks nicer (to me) using [[]] strings.
-- Notice $0 to indicate where the cursor should go at the end of expansion.
["for"] = [[
for ${1:i}, ${2:v} in ipairs(${3:t}) do
$0
end]];
};
c = {
-- Variables can be repeated, and the value of what the user puts in will be
-- expanded at every position where the bare variable is used (i.e. $1, $2...)
["#if"] = [[
#if ${1:CONDITION}
$0
#endif // $1
]];-- Here is where we get to advanced usage. The `|...` block is a transformation
-- which is applied to the result of the variable *at the position*.
-- Inside of this block, the special variable `S` is defined. Its usage should be
-- obvious based on its usage in the following snippet. If not, read #Details below.
--
-- This is an important note:
-- Transformations don't apply to every position for repeated variables, only
-- at which it is defined.
--
-- You'll also see at the bottom `${|S[1]:gsub("%s+", "_")}`. This is a transformation
-- just like above, except that without a variable name, it'll just be evaluated at
-- the end of the snippet expansion. In this example, it's using the value of variable 1
-- and replacing whitespace with underscores.
guard = [[
#ifndef AK_${1:header name|S.v:upper():gsub("%s+", "_")}_H_
#define AK_$1_H_// This is a header for $1
int ${1|S.v:lower():gsub("%s+", "_")} = 123;
$0
#endif // AK_${|S[1]:gsub("%s+", "_")}_H_
]];-- This is also illegal because it makes no sense, adding a transformation
-- to an expression is redundant.
-- ["inc"] = [[#include "${=vim.fn.expand("%:t")|S.v:upper()}"]];-- Just do this instead.
inc = [[#include "${=vim.fn.expand("%:t"):upper()}"]];-- The final important note is the use of negative number variables.
-- Negative variables *never* ask for user input, but otherwise behave
-- like normal variables.
-- This can be useful for storing the value of an expression, and repeating
-- it in multiple locations.
-- The following snippet will ask for the user's input using `input()` *once*,
-- but use the value in multiple places.
user_input = [[hey? ${-1=vim.fn.input("what's up? ")} = ${-1}]];
};
}-- And now for some examples of snippets I actually use.
local snippets = require'snippets'
local U = require'snippets.utils'
snippets.snippets = {
lua = {
req = [[local ${2:${1|S.v:match"([^.()]+)[()]*$"}} = require '$1']];
func = [[function${1|vim.trim(S.v):gsub("^%S"," %0")}(${2|vim.trim(S.v)})$0 end]];
["local"] = [[local ${2:${1|S.v:match"([^.()]+)[()]*$"}} = ${1}]];
-- Match the indentation of the current line for newlines.
["for"] = U.match_indentation [[
for ${1:i}, ${2:v} in ipairs(${3:t}) do
$0
end]];
};
_global = {
-- If you aren't inside of a comment, make the line a comment.
copyright = U.force_comment [[Copyright (C) Ashkan Kiani ${=os.date("%Y")}]];
};
}
```By default no snippets are stored inside of `require'snippets'.snippets`.
You can assign to the the dictionary of the snippets whenever you want, *but you cannot modify it directly.*
What that means is you can do:
`require'snippets'.snippets = {}`
but you cannot do
`require'snippets'.snippets.c.guard = "ifndef boooo"`
If you wish to modify it like that, you can access the dictionary first to get a copy and assign it afterwards, like:
```lua
local S = require'snippets'
local snippets = S.snippets
snippets.c.guard = "ifndef boooo"
S.snippets = snippets
```This is to discourage invalid access patterns.
# Details
A `snippet` is actually a very well defined format specified as a list of either
`string`s or `variable`s. These components will be concatenated together into the
final body of a snippet.A `variable` looks like the following:
```lua
{
order = number;
is_input = nil | boolean;
id = nil | number | string;
default = nil | string | function;
transform = nil | function;
}
```You'll notice that the only required value is `order`, since it defines the order
in which dynamic components should be evaluated. Multiple components can have
the same `order`, in which case they'll be executed in the order they are found
in the list. The value `0` is special, and it indicates the cursor position at
the end of the snippet.The next interesting component is `is_input`, which determines if this is a field
that should be stopped at to ask for user input to evaluate later. If it is
`false` or `nil`, then it won't prompt the user for input.The `default` is a default value to be used for resolving the value of the
component. The `default` **will always be a string** in the process of
evaluating a snippet. If the `default` is a function, it will be evaluated. So
if a function returns `nil`, `""` the empty string will be used as a default.
If the variable is named using `id`, then only the **first** `default` found in
the list will be used, *so be careful*.`transform` is a function which receives a context of the current state of the
snippet up to the point that the transform is being evaluated.
- This context will contain a member `v` representing the value of the current
variable for **named variables only**.
- Otherwise, you can index it with the name of another variable to lookup. e.g.
`transform = function(context) return context[1] end` would return the value
of variable `1`.`id` is the name of the variable. In the parser, this is restricted to numbers
only for simplicity, but technically any value could be used.## Parsing result examples
- `${1} = $1 -> { is_input = true; id = 1; order = 1; }`
- `${-1} = $-1 -> { is_input = false; id = -1; order = -1; }`
- `$0 -> { id = 0; order = 0; }`
- `${1: a placeholder} -> { is_input = true; id = 1; default = " a placeholder" }`
- `${1=os.date()} -> { is_input = true; id = 1; order = 1; default = function(context) return os.date() end; }`
- `${=os.date()} -> { is_input = false; order = -1; default = function(context) return os.date() end; }`
- `${|os.date()} -> { is_input = false; order = math.huge; transform = function(context) return os.date() end; }`
- `${-1|os.date()} -> { is_input = false; id = -1; order = -1; transform = function(context) return os.date() end; }`
- `${-1:asdf|S[1]:upper()} -> { is_input = false; id = -1; order = -1; default = "asdf"; transform = function(context) return context[1]:upper() end; }`
- `${-1:$} -> { is_input = false; id = -1; order = -1; default = "asdf"; transform = function(context) return context[1]:upper() end; }`## Snippet example
```lua
require'snippets.parser'.parse_snippet [[\usepackage[$2]{$1}]] == {
"\\usepackage[", {default = "",id = 2,is_input = true,order = 2},
"]{",
{ default = "",id = 1,is_input = true,order = 1},
"}"
}require'snippets.parser'.parse_snippet [[function${1|vim.trim(S.v):gsub("^%S"," %0")}(${2|vim.trim(S.v)})$0 end]] == {
"function", { default = "",id = 1,is_input = true,order = 1,transform = , = <1>{}},
"(", {default = "",id = 2,is_input = true,order = 2,transform = , = },
")", {default = "",id = 0,is_input = false,order = 0, = },
" end"
}
```## Snippet manipulation example
```lua
local U = require'snippets.utils'local function note_snippet(header)
-- Put a dummy value for -1 and add a default later.
local S = [[
${-1}:
$0
- ashkan, ${=os.date()}]]
S = U.force_comment(S)
S = U.match_indentation(S)
return U.iterate_variables_by_id(S, -1, function(v)
v.default = header
end)
endrequire'snippets'.snippets = {
_global = {
todo = note_snippet "TODO";
note = note_snippet "NOTE";
};
}
```# Advanced
The actual thing which controls the user experience (UX) of asking for input and advancing the snippet
is called an `inserter` by me (because it inserts text by the end if the snippet completes).If you want to use an alternative UX to the default one, you can use, for instance:
```lua
require'snippets'.set_ux(require'snippets.inserters.vim_input')
```I personally like this one. It's the simplest code and pretty straightforward. Give it a shot or
help me write better inserters or write your own by studying the files. The default inserter is
text\_markers.# TODO (because this is considered beta-level software)
- Document the utilities further.
- Parse existing formats like neosnippet/ultisnips maybe...
- Handle consistency across undo points.
- Specifically, I need to be able to record an undo point at the right place
right before a snippet is expanded and then potentially delete the
active\_snippet when an undo is called because we can't guarantee that a snippet
will ever terminate then.
- I could potentially then switch to a stack model of pushing new snippets so you could
do multiple snippets at a time.
- Limit how far the scanning for snippet markers goes.