feat(vim): better snippets
This moves all of the snippets into a yasnippet file format so I can better manage and edit the snippets. I did not like having them all in one file stored as strings. This implements a custom file parser that will convert the snippet file into a luasnip parsed snippet that uses the LSP snippet syntax. It also ports over some of my most used snippets from the emacs config, maybe one day I could share the snippet in vim and emacs.
This commit is contained in:
parent
45802a67ce
commit
afb09f2436
22 changed files with 235 additions and 87 deletions
|
|
@ -1,100 +1,98 @@
|
||||||
local ls = require('luasnip')
|
-- Add lua snippets from a yasnippet style snippet format. This is so I can
|
||||||
local s = ls.snippet
|
-- manage the snippet in file format rather than in json or lua. Supports
|
||||||
local sn = ls.snippet_node
|
-- adding attributes that will be added to `context` of the luasnip. The body
|
||||||
local i = ls.insert_node
|
-- of the snippets are in lsp-snippets format and will be run though the
|
||||||
local f = ls.function_node
|
-- `parse_snippet` function from luasnip
|
||||||
local t = ls.text_node
|
--
|
||||||
local d = ls.dynamic_node
|
-- See: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#snippet_syntax
|
||||||
local fmt = require('luasnip.extras.fmt').fmt
|
-- See: https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#snippets
|
||||||
|
--
|
||||||
|
local ls = require "luasnip"
|
||||||
|
|
||||||
function p (trig, desc, snip)
|
-- Parse a line in the snippet. Will parse a key value attribute in the the line
|
||||||
return ls.parser.parse_snippet(
|
-- formatted `# key: value` if a key is not found then the key will be `body`
|
||||||
{ trig = trig, dscr = desc },
|
-- and the hole line will be added to the value.
|
||||||
table.concat(snip, '\n')
|
local function parse_snippet_line(line)
|
||||||
)
|
-- This is the body separator for the metadata to the body of the snippet
|
||||||
|
if line == "# --" then
|
||||||
|
return { mode = "skip" }
|
||||||
end
|
end
|
||||||
|
|
||||||
ls.config.setup({
|
local key = line:match "#%s+(.*):"
|
||||||
store_selection_keys="<Tab>",
|
-- If there is no key then its part of the body and it needs to be
|
||||||
update_events="InsertLeave,TextChangedI",
|
-- concatenated to the other lines
|
||||||
})
|
if key == nil then
|
||||||
|
return { key = "body", mode = "concat", value = line }
|
||||||
ls.add_snippets("all", {
|
|
||||||
p('todo', 'Todo comment', { 'TODO(${1:ade}): $0' })
|
|
||||||
})
|
|
||||||
|
|
||||||
ls.add_snippets("org", {
|
|
||||||
p('org-header', 'Org mode header block', {
|
|
||||||
'#+TITLE: $0',
|
|
||||||
'#+AUTHOR: Ade Attwood',
|
|
||||||
'#+EMAIL: hello@adeattwood.co.uk',
|
|
||||||
'#+DATE: $CURRENT_YEAR-$CURRENT_MONTH-${CURRENT_DATE}'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
ls.add_snippets("php", {
|
|
||||||
p('#!', 'Shebang', { '#!/usr/bin/env php' }),
|
|
||||||
p( '/**', 'Block comment', {
|
|
||||||
'/**',
|
|
||||||
' * ${0}',
|
|
||||||
' */'
|
|
||||||
}),
|
|
||||||
s(
|
|
||||||
{trig = 'this', dscr = 'This shorthand'},
|
|
||||||
fmt("$this->{}", {
|
|
||||||
i(0),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
s(
|
|
||||||
{trig = 'ai', dscr = 'Array item'},
|
|
||||||
fmt("'{}' => {}", {
|
|
||||||
i(1),
|
|
||||||
i(0),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
local js_ts = {
|
|
||||||
p('#!', 'Shebang', { '#!/usr/bin/env node' }),
|
|
||||||
s(
|
|
||||||
{trig = 'import', dscr = 'Import statement'},
|
|
||||||
fmt("import {} from '{}'", {
|
|
||||||
i(0),
|
|
||||||
i(1)
|
|
||||||
})
|
|
||||||
),
|
|
||||||
|
|
||||||
s({trig = 'fn', dscr = 'Function'}, {
|
|
||||||
t('function '),
|
|
||||||
i(1),
|
|
||||||
t('('),
|
|
||||||
i(2),
|
|
||||||
t(')'),
|
|
||||||
t({' {', '\t'}),
|
|
||||||
i(0),
|
|
||||||
t({'', '}'})
|
|
||||||
}),
|
|
||||||
|
|
||||||
s({trig = 'useState', dscr = 'React useState hook'}, {
|
|
||||||
t('const ['),
|
|
||||||
i(1, 'state'),
|
|
||||||
t(', '),
|
|
||||||
f(function (args)
|
|
||||||
if args[1] == nil then
|
|
||||||
return ''
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return 'set' .. args[1][1]:gsub("^%l", string.upper)
|
local value = line:match ":%s+(.*)$"
|
||||||
end, {1}),
|
|
||||||
t('] = React.useState('),
|
|
||||||
i(0),
|
|
||||||
t(');')
|
|
||||||
}),
|
|
||||||
p('log', 'Console log statement', { 'console.log(${0});' })
|
|
||||||
}
|
|
||||||
|
|
||||||
|
-- Split the string on `,` if its the filetypes so we can add the snippet to
|
||||||
|
-- multiple filetypes
|
||||||
|
if key == "filetypes" then
|
||||||
|
value = vim.split(value, ",")
|
||||||
|
end
|
||||||
|
|
||||||
|
return { key = key, value = value, mode = "set" }
|
||||||
|
end
|
||||||
|
|
||||||
ls.add_snippets("typescriptreact", js_ts)
|
-- Parses a hole snippet file into a snippet table that can be added as luasnip
|
||||||
ls.add_snippets("typescript", js_ts)
|
-- snippet. Header key value attribute are parsed into a table and the body of
|
||||||
|
-- the snippet (anything after the "# --") is added as the `body` as a table
|
||||||
|
-- of lines
|
||||||
|
local function parse_snippet_file(file_path)
|
||||||
|
local file = io.open(file_path, "r")
|
||||||
|
local snippet = {}
|
||||||
|
if file == nil then
|
||||||
|
return snippet
|
||||||
|
end
|
||||||
|
|
||||||
|
for line in file:lines() do
|
||||||
|
local line_content = parse_snippet_line(line)
|
||||||
|
if line_content.mode == "concat" then
|
||||||
|
-- Set the key attribute if it dose not exists already we will get a nil
|
||||||
|
-- error if we try to append to a key that is not in the snippet table
|
||||||
|
if snippet[line_content.key] == nil then
|
||||||
|
snippet[line_content.key] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(snippet[line_content.key], line_content.value)
|
||||||
|
elseif line_content.mode == "set" then
|
||||||
|
snippet[line_content.key] = line_content.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return snippet
|
||||||
|
end
|
||||||
|
|
||||||
|
local snippets = {}
|
||||||
|
local paths = vim.split(vim.fn.glob "~/.config/nvim/snippets/**/*.snippet", "\n")
|
||||||
|
for paths_index = 1, #paths do
|
||||||
|
local file = paths[paths_index]
|
||||||
|
local snippet = parse_snippet_file(file)
|
||||||
|
|
||||||
|
local body = table.concat(snippet.body, "\n")
|
||||||
|
snippet.body = nil
|
||||||
|
|
||||||
|
if snippet.key ~= nil then
|
||||||
|
snippet.trig = snippet.key
|
||||||
|
end
|
||||||
|
|
||||||
|
if snippet.description ~= nil then
|
||||||
|
snippet.desc = snippet.description
|
||||||
|
end
|
||||||
|
|
||||||
|
local parsed = ls.parser.parse_snippet(snippet, body, {})
|
||||||
|
for filetype_index = 1, #snippet.filetypes do
|
||||||
|
local filetype = snippet.filetypes[filetype_index]
|
||||||
|
if snippets[filetype] == nil then
|
||||||
|
snippets[filetype] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(snippets[filetype], parsed)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for filetype, snippets_to_add in pairs(snippets) do
|
||||||
|
ls.add_snippets(filetype, snippets_to_add)
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# name: Block comment
|
||||||
|
# key: /**
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: javascript,javascriptreact,typescript,typescriptreact,php
|
||||||
|
# --
|
||||||
|
/**
|
||||||
|
* $0
|
||||||
|
*/
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: Todo comment
|
||||||
|
# key: todo
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: all
|
||||||
|
# --
|
||||||
|
TODO(${1:ade}): $0
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: Breaking change block in commit message
|
||||||
|
# key: bc
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: gitcommit
|
||||||
|
# --
|
||||||
|
BREAKING CHANGE: ${0:Description}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: Chore commit message
|
||||||
|
# key: chore
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: gitcommit
|
||||||
|
# --
|
||||||
|
chore(${1:scope}): ${2:title}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: Continuous intergration commit message
|
||||||
|
# key: ci
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: gitcommit
|
||||||
|
# --
|
||||||
|
ci(${1:scope}): ${2:title}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: Documentation commit message
|
||||||
|
# key: docs
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: gitcommit
|
||||||
|
# --
|
||||||
|
docs(${1:scope}): ${2:title}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: Feature commit message
|
||||||
|
# key: feat
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: gitcommit
|
||||||
|
# --
|
||||||
|
feat(${1:scope}): ${2:title}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# name: Bug fix commit message
|
||||||
|
# key: fix
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: gitcommit
|
||||||
|
# --
|
||||||
|
fix(${1:scope}): ${2:title}
|
||||||
|
|
||||||
|
${3:discription}
|
||||||
|
|
||||||
|
fixes issue ${3:issue number}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: Performance commit message
|
||||||
|
# key: perf
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: gitcommit
|
||||||
|
# --
|
||||||
|
perf(${1:scope}): ${2:title}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: Refactor commit message
|
||||||
|
# key: refactor
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: gitcommit
|
||||||
|
# --
|
||||||
|
refactor(${1:scope}): ${2:title}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: Security footer in a commit message
|
||||||
|
# key: sec
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: gitcommit
|
||||||
|
# --
|
||||||
|
SECURITY: ${0:Description}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: Code styling commit message
|
||||||
|
# key: style
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: gitcommit
|
||||||
|
# --
|
||||||
|
style(${1:scope}): ${2:title}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: Test commit message
|
||||||
|
# key: test
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: gitcommit
|
||||||
|
# --
|
||||||
|
test(${1:scope}): ${2:title}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: ESM import statement
|
||||||
|
# key: import
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: javascript,javascriptreact,typescript,typescriptreact
|
||||||
|
# --
|
||||||
|
import { $2 } from '${1:package}';${0}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# name: It test function
|
||||||
|
# key: it
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: javascript,javascriptreact,typescript,typescriptreact
|
||||||
|
# --
|
||||||
|
it('${1}', ${2:async}() => {
|
||||||
|
${0}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: Console lot statement
|
||||||
|
# key: log
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: javascript,javascriptreact,typescript,typescriptreact
|
||||||
|
# --
|
||||||
|
console.log(${0});
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
# name: React functional component
|
||||||
|
# key: rfc
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: javascriptreact
|
||||||
|
# --
|
||||||
|
const $1PropTypes = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {React.FC<PropTypes.InferProps<typeof $1PropTypes>>}
|
||||||
|
*/
|
||||||
|
const ${1:Component} = () => {
|
||||||
|
return <div>$0</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
$1.propTypes = $1PropTypes;
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
# name: React functional component
|
||||||
|
# key: rfc
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: typescriptreact
|
||||||
|
# --
|
||||||
|
export interface $1Props {};
|
||||||
|
|
||||||
|
export const ${1:Component}: React.FC<$1Props> = () => {
|
||||||
|
return (
|
||||||
|
${0:<div>Component</div>}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# name: React imports
|
||||||
|
# key: react
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: javascriptreact
|
||||||
|
# --
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: React imports
|
||||||
|
# key: react
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: typescriptreact
|
||||||
|
# --
|
||||||
|
import React from 'react';
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# name: React useState
|
||||||
|
# key: useState
|
||||||
|
# contributor: Ade Attwood <code@adeattwood.co.uk>
|
||||||
|
# filetypes: javascriptreact,typescriptreact
|
||||||
|
# --
|
||||||
|
const [${1:state}, set${1/(.*)/${1:/capitalize}/}] = React.useState($2);$0
|
||||||
Loading…
Reference in a new issue