• Stars
    star
    277
  • Rank 148,288 (Top 3 %)
  • Language
    Lua
  • License
    GNU General Publi...
  • Created about 4 years ago
  • Updated about 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

snippets.nvim

Check the Wiki for examples and contribute your own!

Installation

Plug norcalli/snippets.nvim

Usage:

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 <C-k> and <C-j>
" They are exactly equivalent to:

" <c-k> will either expand the current snippet at the word or try to jump to
" the next position for the snippet.
inoremap <c-k> <cmd>lua return require'snippets'.expand_or_advance(1)<CR>

" <c-j> will jump backwards to the previous field.
" If you jump before the first field, it will cancel the snippet.
inoremap <c-j> <cmd>lua return require'snippets'.advance_snippet(-1)<CR>

The rest of the README is in Lua NOT VIM SCRIPT.

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:

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 strings or variables. These components will be concatenated together into the final body of a snippet.

A variable looks like the following:

{
  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

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 = <function 1>,<metatable> = <1>{}},
    "(", {default = "",id = 2,is_input = true,order = 2,transform = <function 2>,<metatable> = <table 1>},
    ")", {default = "",id = 0,is_input = false,order = 0,<metatable> = <table 1>},
    " end"
}

Snippet manipulation example

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)
end

require'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:

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.

More Repositories

1

nvim-colorizer.lua

The fastest Neovim colorizer.
Lua
2,201
star
2

nvim-terminal.lua

A high performance filetype mode for Neovim which leverages conceal and highlights your buffer with the correct color codes.
Lua
206
star
3

nvim_utils

Lua
108
star
4

nvim-base16.lua

Programmatic lua library for setting base16 themes in Neovim.
Lua
71
star
5

nvim.lua

nvim.lua is a lua module which provides an object which contains shortcut/magic methods that are very useful for mappings.
Lua
52
star
6

profiler.nvim

Lua
38
star
7

neovim-plugin

Lua
35
star
8

twitch-scraper

Program to poll twitch via its API and download streams from channels as they come live.
Rust
32
star
9

typeracer.nvim

Rust
30
star
10

ui.nvim

Lua
14
star
11

ksway

This library provides a convenient interface for quickly making scripts for i3 and sway (since they share an IPC interface API). It will mainly be focused on sway if that compatibility changes.
Rust
13
star
12

lua-sway

A Lua module for interfacing with sway via IPC.
Lua
12
star
13

nvim-snippets.lua

Lua
10
star
14

ractiveify

Browserify transform for ractive components (and by extension templates) which allows for compilation of embedded scripts and styles!
LiveScript
9
star
15

spiceplot

A program written in Go and using plotinum that interprets raw spice files and produces pretty plots in svg, png, pdf, or jpg formats.
Go
7
star
16

enpass-cli

Enpass CLI agent written written in rust!
Rust
6
star
17

scripts

Some scripts. That's it.
Shell
6
star
18

nvim-popterm.lua

Lua
4
star
19

lua-stream

A Lua module inspired by Monix for processing elements in a Stream using reactive style and coroutines.
Lua
4
star
20

quadprog

Rust
4
star
21

qfpga

Quantum FPGA simulator
Python
3
star
22

undead-us-keyboard

Shell
2
star
23

kpostgres_fixture

Postgres fixtures for making temporary databases and temporary services.
Rust
2
star
24

capacitor

Simple capacitor code calculator on the command line written in Go and Python.
Go
2
star
25

raw_tty.rs

Rust crate for modifying tty modes, esp. raw tty mode.
Rust
2
star
26

kmacros

Rust
1
star
27

newtabwindow

JavaScript
1
star
28

number-parse

A fast templated number parsing library in C++.
C
1
star
29

norcalli

1
star
30

rawspice

A Go library for reading and interpreting raw spice files.
Go
1
star
31

matlab.vim

Vim Script
1
star