使用Lua配置Neovim

2023年06月16日星期五晴北京市北京师范大学, 为了更好的编程和写Hexo日志,决定重新配置一下我的Neovim 。 之前由于课程紧张所以直接使用了vim的配置文件和插件,但是后来发现科技在进步,软件在升级,而Neovim 也越来越好用。于是决定使用其更加统一的接口来配置好我的Neovim, 同时本博客会逐步更新教程,力求达到让读都跟着一步一步的操作就可以完成操作。

限于时间关系,在本文的初步形态,借用一些网上的现成的文章,之后会逐步完善。近三天主要参考的网站分别为

  1. 菜鸟教程|Lua教程

  2. 从零开始配置vim(导读)

  3. 你需要知道的使用 lua 配置 neovim 的一切

  4. Snippets in Visual Studio Code

  5. Neovim主题

  6. NeoVim Builtin LSP的基本配置

  7. 行云流水般的NeoVim Builtin LSP操作

  8. Neovim|Diagnostics

  9. Neovim|Main

首先需要说明的是,在进行针对 Neovim 的配置前需要先阅读一下 lua 语言,如果读者具备一定语言基础的话,那读菜鸟教程就很容易了。同时,在阅读网络教程时,不要拿过来就运行原作者的设置,因为你若不知道作者给出的代码的具体作用,那有可能会带来麻烦,同时也有可能原作者所设置的使用习惯并不一定适用你,所以为了安全和个人使用习惯请理解作者意图后再自行配置系统。

目录树

考虑到目前 Neovim 并不是百分之百的使用 Lua 实现各种功能的插件,所以有时候就不可避免的使用原 vim 的插件,这样为了保持兼容性,我采用了这样的目录树:

  1. Neovim 的配置目录为:~/.config/nvim/

  2. 在配置目录下,建立配置文件入口,使用~/.config/nvim/init.vim 而不是像网上的教程一样使用init.lua,因为这样设置可以最大程度的保证插件的兼容性。文件如下

~/.config/nvim/init.vim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
"set runtimepath^=~/.vim runtimepath+=~/.vim/after
"let &packpath = &runtimepath
"source ~/.vimrc
"let g:loaded_ruby_provider = 0
" neovim 基础设置
lua require('lsp/base')
" Packer 插件管理
lua require('plg/plugins')
lua require('lsp/setup')
lua require('plg/cmp')
"lua require('plugin-config/lspsaga')
" 启用tokyonight主题
lua vim.cmd[[colorscheme tokyonight]]
lua require('plg/tokyonight')
lua require'lspconfig'.pyright.setup{}
"
" set for vim-vsnip cite from README
" NOTE: You can use other key to expand snippet.

" Expand
imap <expr> <C-j> vsnip#expandable() ? '<Plug>(vsnip-expand)' : '<C-j>'
smap <expr> <C-j> vsnip#expandable() ? '<Plug>(vsnip-expand)' : '<C-j>'

" Expand or jump
imap <expr> <C-l> vsnip#available(1) ? '<Plug>(vsnip-expand-or-jump)' : '<C-l>'
smap <expr> <C-l> vsnip#available(1) ? '<Plug>(vsnip-expand-or-jump)' : '<C-l>'

" Jump forward or backward
imap <expr> <Tab> vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>'
smap <expr> <Tab> vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>'
imap <expr> <S-Tab> vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-Tab>'
smap <expr> <S-Tab> vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-Tab>'

" Select or cut text to use as $TM_SELECTED_TEXT in the next snippet.
" See https://github.com/hrsh7th/vim-vsnip/pull/50
nmap s <Plug>(vsnip-select-text)
xmap s <Plug>(vsnip-select-text)
nmap S <Plug>(vsnip-cut-text)
xmap S <Plug>(vsnip-cut-text)

" If you want to use snippet for multiple filetypes, you can `g:vsnip_filetypes` for it.
let g:vsnip_filetypes = {}
let g:vsnip_filetypes.javascriptreact = ['javascript']
let g:vsnip_filetypes.typescriptreact = ['typescript']
  1. ~/.config/nvim/下建立目录lua,同时在lua下建立两个目录lspplg,其中前者用来存放和language service相关的lua配置文件,后者用来存放和插件plugin相关的文件。
~/.config/nvim/plg/plugins.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
return require('packer').startup(function()
-- Packer can manage itself
use 'wbthomason/packer.nvim'
-- Lspconfig & maso & mason-lspconfig
use {
"neovim/nvim-lspconfig",
"williamboman/mason.nvim",
"williamboman/mason-lspconfig.nvim",
}
-- 添加latex插件vimtex
--use {'lervag/vimtex'}
use {'mhinz/neovim-remote'}
-- 添加neovim主题tokyonight
use {'https://gitee.com/rux_plugin/tokyonight.nvim'}
-- -- nvim-cmp
use 'https://gitee.com/nvim-plugin/cmp-nvim-lsp' -- { name = nvim_lsp }
use 'https://gitee.com/nvim-plugin/cmp-buffer' -- { name = 'buffer' },
use 'https://gitee.com/uomer/cmp-path' -- { name = 'path' }
use 'https://gitee.com/cangmj/cmp-cmdline' -- { name = 'cmdline' }
use 'https://gitee.com/uomer/nvim-cmp'
-- vsnip
use 'https://gitee.com/dw8603/cmp-vsnip' -- { name = 'vsnip' }
use 'https://gitee.com/dw8603/vim-vsnip'
use 'https://gitee.com/giteeguangwei/friendly-snippets'
-- lspkind
use 'https://gitee.com/william135/lspkind.nvim'
-- lsp UI美化
use{'https://gitee.com/william135/lspsaga.nvim'}
-- 借用了vim插件,因为二者是兼容的,速度上vim-latex更快
use{'https://gitee.com/fengzhenhua/vim-latex'}
-- 自动插入模板
use{'https://gitee.com/fengzhenhua/vim-template'}
-- 旧式片段插入程序
use{'https://gitee.com/fengzhenhua/ultisnips'}
use{'https://gitee.com/fengzhenhua/vim-snippets'}
end)
~/.config/nvim/plg/cmp.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

local cmp = require("cmp")
local lspkind = require("lspkind")
cmp.setup({
-- 设置代码片段引擎,用于根据代码片段补全
snippet = {
expand = function(args)
-- For `vsnip` users.
vim.fn["vsnip#anonymous"](args.body)

-- For `luasnip` users.
-- require('luasnip').lsp_expand(args.body)

-- For `ultisnips` users.
--vim.fn["UltiSnips#Anon"](args.body)

-- For `snippy` users.
-- require'snippy'.expand_snippet(args.body)
end,
},

window = {
},

mapping = {
-- 选择上一个
['<C-p>'] = cmp.mapping.select_prev_item(),
-- 选择下一个
['<C-n>'] = cmp.mapping.select_next_item(),
-- 出现补全
['<A-.>'] = cmp.mapping(cmp.mapping.complete(), {'i', 'c'}),
-- 取消补全
['<A-,>'] = cmp.mapping({
i = cmp.mapping.abort(),
c = cmp.mapping.close(),
}),

-- 确认使用某个补全项
['<CR>'] = cmp.mapping.confirm({
select = true,
behavior = cmp.ConfirmBehavior.Replace
}),

-- 向上翻页
['<C-u>'] = cmp.mapping(cmp.mapping.scroll_docs(-4), {'i', 'c'}),
-- 向下翻页
['<C-d>'] = cmp.mapping(cmp.mapping.scroll_docs(4), {'i', 'c'}),
},

-- 补全来源
sources = cmp.config.sources({
{name = 'nvim_lsp'},
{name = 'vsnip'},
{name = 'buffer'},
{name = 'path'}
}),

--根据文件类型来选择补全来源
cmp.setup.filetype('gitcommit', {
sources = cmp.config.sources({
{name = 'nvim_lsp'},
{name = 'vsnip'},
{name = 'buffer'}
})
}),

-- 命令模式下输入 `/` 启用补全
cmp.setup.cmdline('/', {
mapping = cmp.mapping.preset.cmdline(),
sources = {
{ name = 'buffer' }
}
}),

-- 命令模式下输入 `:` 启用补全
cmp.setup.cmdline(':', {
mapping = cmp.mapping.preset.cmdline(),
sources = cmp.config.sources({
{ name = 'path' }
}, {
{ name = 'cmdline' }
})
}),

-- 设置补全显示的格式
formatting = {
format = lspkind.cmp_format({
with_text = true,
maxwidth = 50,
before = function(entry, vim_item)
vim_item.menu = "[" .. string.upper(entry.source.name) .. "]"
return vim_item
end
}),
},
})
~/.config/nvim/lua/plg/lspsaga.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

local saga = require('lspsaga')
saga.init_lsp_saga()
lsp_keybinds.set_keymap = function (bufnr)
print("set lsp keymap")
-- 跳转到声明
vim.api.nvim_buf_set_keymap(bufnr, "n", "gd", "<cmd>Lspsaga peek_definition<CR>", {silent = true, noremap = true})
-- 跳转到定义
vim.api.nvim_buf_set_keymap(bufnr, "n", "gD", "<cmd>lua vim.lsp.buf.definition()<CR>", {silent = true, noremap = true})
-- 显示注释文档
vim.api.nvim_buf_set_keymap(bufnr, "n", "gh", "<cmd>Lspsaga lsp_finder<CR>", {silent = true, noremap = true})
-- 跳转到实现
vim.api.nvim_buf_set_keymap(bufnr, "n", "gi", "<cmd>lua vim.lsp.buf.implementation()<CR>", {silent = true, noremap = true})
-- 跳转到引用位置
vim.api.nvim_buf_set_keymap(bufnr, "n", "gr", "<cmd>Lspsaga rename<CR>", {silent = true, noremap = true})
-- 以浮窗形式显示错误
vim.api.nvim_buf_set_keymap(bufnr, "n", "go", "<cmd>lua vim.diagnostic.open_float()<CR>", {silent = true, noremap = true})
vim.api.nvim_buf_set_keymap(bufnr, "n", "gp", "<cmd>lua vim.diagnostic.goto_prev()<CR>", {silent = true, noremap = true})
vim.api.nvim_buf_set_keymap(bufnr, "n", "gn", "<cmd>lua vim.diagnostic.goto_next()<CR>", {silent = true, noremap = true})
vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>cd", "<cmd>Lspsaga show_cursor_diagnostics<CR>", {silent = true, noremap = true})
vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>cd", "<cmd>Lspsaga show_line_diagnostics<CR>", {silent = true, noremap = true})
vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>ca", "<cmd>Lspsaga code_action<CR>", {silent = true, noremap = true})
vim.api.nvim_buf_set_keymap(bufnr, "v", "<leader>ca", "<cmd>Lspsaga code_action<CR>", {silent = true, noremap = true})
end
~/.config/nvim/lua/plg/tokyonight.lua
1
2
3
4
5
6
-- 配置主题颜色模式为 
vim.g.tokyonight_style = "storm"
-- 允许neovim中的终端使用该主题配色
vim.g.tokyonight_terminal_colors = true
-- 注释使用斜体
vim.g.tokyonight_italic_comments = true
~/.config/nvim/lua/lsp/setup.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

-- :h mason-default-settings
-- ~/.local/share/nvim/mason
--
local servers = {
lua_ls = require "lsp.lua", -- /lua/lsp/lua.lua
ltex = require "lsp.ltex", -- /lua/lsp/ltex.lua
}

require("mason").setup({
ui = {
icons = {
package_installed = "✓",
package_pending = "➜",
package_uninstalled = "✗"
}
}
})

-- 添加安装的插件,使用Mason命令查询插件名称
require("mason-lspconfig").setup {
ensure_installed = {
"lua_ls",
"rust_analyzer",
"ltex",
"pyright",
"vimls"
},
}


local lspconfig = require('lspconfig')

require("mason-lspconfig").setup_handlers({
function (server_name)
require("lspconfig")[server_name].setup{}
end,
-- Next, you can provide targeted overrides for specific servers.
-- set lua_ls
["lua_ls"] = function ()
lspconfig.lua_ls.setup {
settings = {
Lua = {
diagnostics = {
globals = { "vim" }
}
}
}
}
end,
-- set clangd
["clangd"] = function ()
lspconfig.clangd.setup {
cmd = {
"clangd",
"--header-insertion=never",
"--query-driver=/opt/homebrew/opt/llvm/bin/clang",
"--all-scopes-completion",
"--completion-style=detailed",
}
}
end
})

~/.config/nvim/lua/lsp/base.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

-- 设置文件编码格式为 utf-8
vim.g.encoding = "utf-8"
-- 设置终端编码格式为 utf-8
vim.o.termencoding = "utf-8"
-- 开启语法高亮
vim.o.syntax = "enable"
-- 显示相对行号
vim.o.relativenumber = true
-- 显示行号
vim.o.number = true
-- 高亮所在行
vim.o.cursorline = true
-- 自动换行
vim.o.wrap = true
-- 显示光标位置
vim.o.ruler = true
-- 边输入边搜索
vim.o.incsearch = true
-- 开启搜索匹配高亮
vim.o.hlsearch = true
-- 搜索时自行判断是否需要忽略大小写
vim.o.smartcase = true

-- tab键转换为 4 个空格
vim.o.tabstop = 4
vim.o.softtabstop = 4
vim.o.shiftwidth = 4
-- 新行对齐当前行,tab转换为空格
vim.o.expandtab = true
vim.bo.expandtab = true
vim.o.autoindent = true
vim.bo.autoindent = true
vim.o.smartindent = true

-- << >> 缩进时移动的长度
vim.o.shiftwidth = 4
vim.bo.shiftwidth = 4

-- 使用jk移动光标时,上下方保留8行
vim.o.scrolloff = 8
vim.o.sidescrolloff = 8

-- 设置自动折叠
vim.o.smartindent = true
-- 历史命令最多保存1000条
vim.o.history = 1000
-- 显示空白字符
vim.o.list = true
-- 样式
vim.o.background = "dark"
vim.o.termguicolors = true
vim.opt.termguicolors = true
~/.config/nvim/lua/lsp/lua.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

local runtime_path = vim.split(package.path, ';')
table.insert(runtime_path, "lua/?.lua")
table.insert(runtime_path, "lua/?/init.lua")
return {
settings = {
Lua = {
runtime = {
-- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
version = 'LuaJIT',
-- Setup your lua path
path = runtime_path,
},
diagnostics = {
-- Get the language server to recognize the `vim` global
globals = {'vim'},
},
workspace = {
-- Make the server aware of Neovim runtime files
library = vim.api.nvim_get_runtime_file("", true),
},
-- Do not send telemetry data containing a randomized but unique identifier
telemetry = {
enable = false
},
},
},
}
~/.config/nvim/lua/lsp/lua.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

local util = require 'lspconfig.util'

local language_id_mapping = {
bib = 'bibtex',
plaintex = 'tex',
rnoweb = 'sweave',
rst = 'restructuredtext',
tex = 'latex',
xhtml = 'xhtml',
pandoc = 'markdown',
}

local bin_name = 'ltex-ls'
if vim.fn.has 'win32' == 1 then
bin_name = bin_name .. '.bat'
end

return {
default_config = {
cmd = { bin_name },
filetypes = { 'bib', 'gitcommit', 'markdown', 'org', 'plaintex', 'rst', 'rnoweb', 'tex', 'pandoc' },
root_dir = util.find_git_ancestor,
single_file_support = true,
get_language_id = function(_, filetype)
local language_id = language_id_mapping[filetype]
if language_id then
return language_id
else
return filetype
end
end,
},
docs = {
description = [=[
https://github.com/valentjn/ltex-ls

LTeX Language Server: LSP language server for LanguageTool 🔍✔️ with support for LaTeX 🎓, Markdown 📝, and others

To install, download the latest [release](https://github.com/valentjn/ltex-ls/releases) and ensure `ltex-ls` is on your path.

This server accepts configuration via the `settings` key.

1
2
3
4
5
settings = {
ltex = {
language = "en-GB",
},
},


To support org files or R sweave, users can define a custom filetype autocommand (or use a plugin which defines these filetypes):

1
vim.cmd [[ autocmd BufRead,BufNewFile *.org set filetype=org ]]

]=],
},
}
~/.config/nvim/lua/lsp/python.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

local lsp_set_keymap = require("keybindings")
local util = require 'lspconfig/util'

require('lspconfig').pyright.setup{
on_attach = function(_, bufnr)
lsp_set_keymap.set_keymap(bufnr)
end,
cmd = { "pyright-langserver", "--stdio" },
filetypes = { "python" },
settings = {
python = {
analysis = {
autoSearchPaths = true,
diagnosticMode = "workspace",
useLibraryCodeForTypes = true,
typeCheckingMode = "off"
},
},
},
root_dir = function(fname)
local root_files = {
'pyproject.toml',
'setup.py',
'setup.cfg',
'requirements.txt',
'Pipfile',
'pyrightconfig.json',
}
return util.root_pattern(unpack(root_files))(fname) or util.find_git_ancestor(fname) or util.path.dirname(fname)
end,
}
~/.config/nvim/lua/lsp/keybindings.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

local pluginKeys = {}

-- lsp 回调函数快捷键设置
pluginKeys.maplsp = function(mapbuf)
-- rename
mapbuf('n', '<leader>rn', '<cmd>lua vim.lsp.buf.rename()<CR>', opt)
-- code action
mapbuf('n', '<space>ca', '<cmd>lua vim.lsp.buf.code_action()<CR>', opt)
-- go xx
mapbuf('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<CR>', opt)
mapbuf('n', 'gh', '<cmd>lua vim.lsp.buf.hover()<CR>', opt)
mapbuf('n', 'gD', '<cmd>lua vim.lsp.buf.declaration()<CR>', opt)
mapbuf('n', 'gi', '<cmd>lua vim.lsp.buf.implementation()<CR>', opt)
mapbuf('n', 'gr', '<cmd>lua vim.lsp.buf.references()<CR>', opt)
-- diagnostic
mapbuf('n', 'go', '<cmd>lua vim.diagnostic.open_float()<CR>', opt)
mapbuf('n', 'gp', '<cmd>lua vim.diagnostic.goto_prev()<CR>', opt)
mapbuf('n', 'gn', '<cmd>lua vim.diagnostic.goto_next()<CR>', opt)
-- mapbuf('n', '<leader>q', '<cmd>lua vim.diagnostic.setloclist()<CR>', opt)
-- leader + =
mapbuf('n', '<leader>=', '<cmd>lua vim.lsp.buf.formatting()<CR>', opt)
-- mapbuf('n', '<C-k>', '<cmd>lua vim.lsp.buf.signature_help()<CR>', opt)
-- mapbuf('n', '<space>wa', '<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>', opt)
-- mapbuf('n', '<space>wr', '<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>', opt)
-- mapbuf('n', '<space>wl', '<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>', opt)
-- mapbuf('n', '<space>D', '<cmd>lua vim.lsp.buf.type_definition()<CR>', opt)
end

return pluginKeys
  1. 下面是我本人针对通用文件和Markdown文件编写的代码片段,前者是一些通用命令,后者是在写博客时针对Next主题设置的特定模块。
~/.vsnip/global.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"date": {
"prefix": "date",
"body": [
"${VIM:system('date')}"
]
},
"weather": {
"prefix": "weather",
"body": [
"${VIM:system('weather')}"
]
},
"uuid": {
"prefix": "uuid",
"body": [
"${VIM:system('python -c \"import uuid, sys;sys.stdout.write(str(uuid.uuid4()))\"')}"
]
},
"filename": {
"prefix": ["fname"],
"body": "$TM_FILENAME_BASE"
}
}
~/.vsnip/markdown.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
{
"code": {
"prefix": ["code"],
"body": [
"{% code <+title+> lang:${1|bash,python,lua,perl,yaml,ruby|} %}",
"<+code+>",
"{% endcode %}"
],
"description": "Next theme environment for Hexo"
},
"codeblock": {
"prefix": ["codeblock"],
"body": [
"{% codeblock <+title+> lang:${1|bash,python,lua,perl,yaml,ruby|} %}",
"<+code+>",
"{% endcodeblock %}"
],
"description": "Next theme environment for Hexo"
},
"```": {
"prefix": ["```"],
"body": [
"```${1|bash,python,lua,perl,yaml,ruby|} <+title+> ",
"<+code+>",
"```"
],
"description": "Next theme environment for Hexo"
},
"note": {
"prefix": ["note"],
"body": [
"{% note ${1|default,primary,success,info,warning,danger|} %}",
"<++>",
"{% endnote %}"
],
"description": "Next theme environment for Hexo"
},
"tabs": {
"prefix": ["tabs"],
"body": [
"{% tabs ${1:<+UniqueName+>} %}",
"<!-- tab ${2:<+tabcaption+>} -->",
"<++>",
"<!-- endtab -->",
"<++>",
"{% endtabs %}"
],
"description": "Next theme environment for Hexo"
},
"tab": {
"prefix": ["tab"],
"body": [
"<!-- tab ${2:<+tabcaption+>} -->",
"<++>",
"<!-- endtab -->"
],
"description": "Next theme environment for Hexo"
},

"label": {
"prefix": ["label"],
"body": [
"{% label ${1|default,primary,success,info,warning,danger|}@${2:<+content+>} %}"
],
"description": "Next theme environment for Hexo"
},

"centerquote": {
"prefix": ["centerquote"],
"body": [
"{% centerquote %}",
"${1:<+content+>}",
"{% endcenterquote %}"
],
"description": "Next theme environment for Hexo"
},

"video": {
"prefix": ["video"],
"body": [
"{% video ${1|<+/path/to/your/video.mp4+>,https://<+example.com/sample.mp4+>|}%}"
],
"description": "Next theme environment for Hexo"
},

"pdf": {
"prefix": ["pdf"],
"body": [
"{% pdf ${1|<+/path/to/your/video.pdf+>,https://<+example.com/sample.pdf+>|}%}"
],
"description": "Next theme environment for Hexo"
},

"grouppicture": {
"prefix": ["grouppicture"],
"body": [
"{% grouppicture ${1:<+number+>}-${2:<+layout+>} %}",
"![${3:<+picturetitle+>}](${4:<+picture+>})",
"{% endgroudpicture %}"
],
"description": "Next theme environment for Hexo"
}
}