使用Lua给neovim写插件

最近在使用neovim和LazyVim配置写代码,感觉很不错. 得益于neovim简单的功能和强大的第三方生态,可以很简单的写出一个插件. 这里根据官方文档搭配lazyvim等资料写一个简单插件.

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
l1 = {a=2,b=4}
l2 = {a=2,b=4}
setmetatable(l1,{__add=function(f1,f2)
sum = {}
sum.a = f1.a + f2.a
sum.b = f1.b + f2.b
return sum
end
})
-- Values of __index,add, .. are called metamethods.
-- Full list. Here a is a table with the metamethod.

-- __add(a, b) for a + b
-- __sub(a, b) for a - b
-- __mul(a, b) for a * b
-- __div(a, b) for a / b
-- __mod(a, b) for a % b
-- __pow(a, b) for a ^ b
-- __unm(a) for -a
-- __concat(a, b) for a .. b
-- __len(a) for #a
-- __eq(a, b) for a == b
-- __lt(a, b) for a < b
-- __le(a, b) for a <= b
-- __index(a, b) <fn or a table> for a.b
-- __newindex(a, b, c) for a.b = c
-- __call(a, ...) for a(...)
local Dog = {}
function Dog:new()
newObj = {name=""}
self.__index = self
return setmetatable(newObj,self)
end
function Dog:makeSound()
print('I say ' .. self.sound)
end
LoudDog = Dog:new()
function LoudDog:makeSound()
s = self.sound .. ' '
print(s .. s .. s)
end
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
-- Suppose the file mod.lua looks like this:
local M = {}

local function sayMyName()
print('Hrunkner')
end

function M.sayHello()
print('Why hello there')
sayMyName()
end

return M

-- Another file can use mod.lua's functionality:
local mod = require('mod')
-- require's return values are cached so a file is
-- run at most once, even when require'd many times.

-- Suppose mod2.lua contains "print('Hi!')".
local a = require('mod2') -- Prints Hi!
local b = require('mod2') -- Doesn't print; a=b.

-- dofile is like require without caching:
dofile('mod2.lua') --> Hi!
dofile('mod2.lua') --> Hi! (runs it again)

-- loadfile loads a lua file but doesn't run it yet.
f = loadfile('mod2.lua') -- Call f() to run it.

-- load is loadfile for strings.
-- (loadstring is deprecated, use load instead)
g = loadstring('print(343)') -- Returns a function.
g() -- Prints out 343; nothing printed before now.

重要函数

重要的库: io,string.os,table,math

函数作用
stringstring.upper(s)Converts s to uppercase
string.lower(s)Converts s to lowercase
string.len(s)Returns the length of s
mathmath.max(x, …)Returns the maximum value among its arguments
math.min(x, …)Returns the minimum value among its arguments
math.random([m [, n]])Generates a random number
tabletable.insert(t, [pos,] value)Inserts value into t at position pos
table.remove(t [, pos])Removes from t the element at position pos
table.sort(t [, comp])Sorts table elements in a given order

require

在 Lua 中,require 函数用于加载和初始化模块。它会按照一定的顺序搜索指定的模块,并根据配置从不同的目录中加载模块文件。具体来说,require 的查找路径由 package.pathpackage.cpath 两个变量定义。

package.path

  • 用途:用于指定 Lua 模块(.lua 文件)的搜索路径。

  • 默认值:在不同平台和 Lua 版本上,默认值可能有所不同,但通常类似于以下格式:

    1
    2
    3
    4
    5
    6
    7
    8
    package.path = './?.lua;' .. -- 当前目录下的 .lua 文件
    './?/init.lua;' .. -- 当前目录下的子目录中的 init.lua 文件
    '/usr/local/share/lua/5.4/?.lua;' ..
    '/usr/local/share/lua/5.4/?/init.lua;' ..
    '/usr/local/lib/lua/5.4/?.lua;' ..
    '/usr/local/lib/lua/5.4/?/init.lua;' ..
    './lua/?.lua;' ..
    './lua/?/init.lua'
  • 模式说明

    • ? 是一个占位符,代表模块名。
    • ?/init.lua 表示如果模块名是目录,则尝试加载该目录下的 init.lua 文件。
  1. package.cpath
  • 用途:用于指定 C 模块(即使用 C 或 C++ 编写的模块,通常是 .so.dll 文件)的搜索路径。

  • 默认值:同样地,这取决于平台和 Lua 版本,但一般包括如下路径:

    1
    2
    3
    4
    5
    package.cpath = './?.so;' .. -- 当前目录下的共享库文件
    './?.dll;' .. -- Windows 上的动态链接库文件
    './loadall.so;' .. -- 加载所有符号的共享库
    '/usr/local/lib/lua/5.4/?.so;' ..
    '/usr/local/lib/lua/5.4/loadall.so;'
  • 模式说明

    • ? 同样作为模块名的占位符。
    • 其他部分指定了操作系统特定的库文件扩展名。
  1. package.preload

除了上述路径外,require 还会在 package.preload 表中查找是否已经预加载了相应的模块。如果找到了匹配项,则直接返回对应的函数而不进行文件系统搜索

Neovim的基本功能

主要组件

image-20250118174705262

基础配置

Nvim 支持使用 init.vim 或 init.lua 作为配置文件,但不能同时使用这两个文件。 该文件应放在config目录中、
对于 Linux、BSD 或 macOS,该目录通常为 ~/.config/nvim,而对于
/AppData/Local/nvim/(Windows)。 请注意,可以在 init.vim 中使用 Lua
中使用 Lua,在 init.lua 中使用 Vimscript。 如果想在启动时自动运行任何其他 Lua 脚本,那么
只需将其放入运行时路径中的 plugin/。

nvim中的lua

Lua-guide - Neovim docs

vim.g

vim.g 是一个 Lua 表,它提供了对全局变量的访问。通过 vim.g,你可以设置或读取任何定义在全局命名空间中的变量。这对于配置插件或共享状态信息非常有用,因为全局变量可以在整个会话期间保持不变,并且可以在不同的脚本之间传递数据。

vim.cmd

执行vimscript

1
2
3
4
5
6
7
8
9
10
11
12
13
vim.cmd('echo 42')
vim.cmd([[
augroup My_group
autocmd!
autocmd FileType c setlocal cindent
augroup END
]])
-- Ex command :echo "foo"
-- Note string literals need to be double quoted.
vim.cmd('echo "foo"')
vim.cmd { cmd = 'echo', args = { '"foo"' } }
vim.cmd.echo({ args = { '"foo"' } })
vim.cmd.echo('"foo"')

vim.opt

vim.opt 是另一个 Lua 表,但它专注于编辑器的选项配置。它允许用户以一种结构化的方式设置和查询 Vim/Neovim 的各种行为参数。与传统的 :set 命令相比,vim.opt 提供了一个更加直观和易于维护的方式来管理和调整编辑器的行为.可以指定选项的作用范围,比如 vim.o 对于全局选项,vim.bo 对于缓冲区特定选项,vim.wo 对于窗口特定选项

一个特殊的接口 vim.opt,可以方便地与 Lua 中的列表和映射样式选项进行交互:它允许以 Lua 表格的形式访问它们,并提供面向对象的方法来添加和删除条目

In Lua using vim.o:

1
vim.o.wildignore = '*.o,*.a,__pycache__'

In Lua using vim.opt:

1
vim.opt.wildignore = { '*.o', '*.a', '__pycache__' }

vim.api

vim.api

vim.api.{func}({...})
调用带有参数 {…} 的 Nvim API 函数 {func}。 示例:调用 “nvim_get_current_line()” API 函数:

1
print(tostring(vim.api.nvim_get_current_line()))

vim.fn

vim.fn 是一个特别设计用来桥接 Vimscript 和 Lua 的接口。它使得开发者能够在 Lua 脚本中调用所有可用的 Vim 内置函数。

  • 执行系统命令并捕获输出:local output = vim.fn.system('uname -a')
  • 获取当前文件名:local filename = vim.fn.expand('%')
  • 访问环境变量:local path = vim.fn.getenv('PATH')

Neovim会默认从某个目录中加载init.lua,路径如下

OSPATH
Linux, MacOS$XDG_CONFIG_HOME/nvim, ~/.config/nvim
Windows (cmd)%localappdata%\nvim\
Windows (powershell)$env:LOCALAPPDATA\nvim\

vim.system

vim.system({cmd}, {opts}, {on_exit})
运行系统命令,如果 {cmd} 无法运行,则抛出错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
local on_exit = function(obj)
print(obj.code)
print(obj.signal)
print(obj.stdout)
print(obj.stderr)
end

-- Runs asynchronously:
vim.system({'echo', 'hello'}, { text = true }, on_exit)

-- Runs synchronously:
local obj = vim.system({'echo', 'hello'}, { text = true }):wait()
-- { code = 0, signal = 0, stdout = 'hello', stderr = '' }

vim.uv vim.loop

vim.uv暴露 Nvim 用于网络、文件系统和进程管理的 libUV 库的 “luv” Lua 绑定.

vim.uv 是 Neovim 提供的一个接口,它封装了 libuv 库的功能。libuv 是一个用于异步 I/O 的多平台支持库,最初为 Node.js 开发,但因其高效性和跨平台特性而被广泛采用。通过 vim.uv,Neovim 用户和插件开发者可以直接访问底层的文件系统、网络和其他系统操作功能,从而实现更复杂的应用逻辑或优化性能关键部分

vim.env

编辑器会话中定义的环境变量

vim.schedule

通过事件循环执行函数,避免阻塞

vim.lsp

Nvim 支持语言服务器协议 (LSP),这意味着它充当 LSP 服务器的客户端,并包含一个 Lua 框架 vim.lsp 用于构建增强的 LSP 工具

vim.tbl_deep_extend

Neovim 提供的一个用于深度合并 Lua 表的函数。它允许你将一个或多个表的内容递归地合并到目标表中,而不会简单地覆盖原有的键值对。这对于配置管理、插件开发以及其他需要合并多层级数据结构的场景非常有用

1
vim.tbl_deep_extend(strategy, target, source1, source2, ...)
  • strategy

    :指定合并策略,可以是以下之一:

    • "force":强制覆盖目标表中的现有键值。
    • "keep":保留目标表中的现有键值,不被源表覆盖。
    • "error":如果遇到冲突(即同一个键存在于目标和源表中),则抛出错误。
  • target:目标表,即将要接收合并结果的表。

  • source1, source2, …:一个或多个源表,它们的内容将被合并到目标表中。

kickstart

kickstart是配置nvim很好的一个参考. 要点:勤用:help,:help lua-guide,可以查阅neovim提供的lua函数

配置全局变量与选项(vim.g&&vim.opt)

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
-- Set <space> as the leader key
-- See `:help mapleader`
-- NOTE: Must happen before plugins are loaded (otherwise wrong leader will be used)
vim.g.mapleader = ' '
vim.g.maplocalleader = ' '

-- Set to true if you have a Nerd Font installed and selected in the terminal
vim.g.have_nerd_font = false

-- [[ Setting options ]]
-- See `:help vim.opt`
-- NOTE: You can change these options as you wish!
-- For more options, you can see `:help option-list`

-- Make line numbers default
vim.opt.number = true
-- You can also add relative line numbers, to help with jumping.
-- Experiment for yourself to see if you like it!
-- vim.opt.relativenumber = true

-- Enable mouse mode, can be useful for resizing splits for example!
vim.opt.mouse = 'a'

-- Don't show the mode, since it's already in the status line
vim.opt.showmode = false

image-20250118233131109

快捷键(keymap)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- [[ Basic Keymaps ]]
-- See `:help vim.keymap.set()`

-- Clear highlights on search when pressing <Esc> in normal mode
-- See `:help hlsearch`
vim.keymap.set('n', '<Esc>', '<cmd>nohlsearch<CR>')

-- Diagnostic keymaps
vim.keymap.set('n', '<leader>q', vim.diagnostic.setloclist, { desc = 'Open diagnostic [Q]uickfix list' })

-- Exit terminal mode in the builtin terminal with a shortcut that is a bit easier
-- for people to discover. Otherwise, you normally need to press <C-\><C-n>, which
-- is not what someone will guess without a bit more experience.
--
-- NOTE: This won't work in all terminal emulators/tmux/etc. Try your own mapping
-- or just use <C-\><C-n> to exit terminal mode
vim.keymap.set('t', '<Esc><Esc>', '<C-\\><C-n>', { desc = 'Exit terminal mode' })

-- TIP: Disable arrow keys in normal mode
-- vim.keymap.set('n', '<left>', '<cmd>echo "Use h to move!!"<CR>')
-- vim.keymap.set('n', '<right>', '<cmd>echo "Use l to move!!"<CR>')
-- vim.keymap.set('n', '<up>', '<cmd>echo "Use k to move!!"<CR>')
-- vim.keymap.set('n', '<down>', '<cmd>echo "Use j to move!!"<CR>')

指令执行(autocmd)

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
-- [[ Basic Autocommands ]]
-- See `:help lua-guide-autocommands`

-- Highlight when yanking (copying) text
-- Try it with `yap` in normal mode
-- See `:help vim.highlight.on_yank()`
vim.api.nvim_create_autocmd('TextYankPost', {
desc = 'Highlight when yanking (copying) text',
group = vim.api.nvim_create_augroup('kickstart-highlight-yank', { clear = true }),
callback = function()
vim.highlight.on_yank()
end,
})

vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
pattern = {"*.c", "*.h"},
command = "echo 'Entering a C or C++ file'",
})

-- Same autocommand written with a Lua function instead
vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
pattern = {"*.c", "*.h"},
callback = function() print("Entering a C or C++ file") end,
})

-- User event triggered by MyPlugin
vim.api.nvim_create_autocmd("User", {
pattern = "MyPlugin",
callback = function() print("My Plugin Works!") end,
})

自动命令是 Vim 命令或 Lua 函数,每当触发一个或多个事件触发时自动执行的 Vim 命令或 Lua 函数。
读取或写入文件,或创建窗口时自动执行。 可以通过Lua使用Nvim API 访问。

安装包管理器

1
2
3
4
5
6
7
8
9
10
11
-- [[ Install `lazy.nvim` plugin manager ]]
-- See `:help lazy.nvim.txt` or https://github.com/folke/lazy.nvim for more info
local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim'
if not (vim.uv or vim.loop).fs_stat(lazypath) then
local lazyrepo = 'https://github.com/folke/lazy.nvim.git'
local out = vim.fn.system { 'git', 'clone', '--filter=blob:none', '--branch=stable', lazyrepo, lazypath }
if vim.v.shell_error ~= 0 then
error('Error cloning lazy.nvim:\n' .. out)
end
end ---@diagnostic disable-next-line: undefined-field
vim.opt.rtp:prepend(lazypath)

vim.fn.stdpath 用于获取标准路径的绝对路径。它可以帮助你轻松找到配置文件、数据文件或缓存文件所在的目录,这对于编写可移植的脚本和插件非常有用。

kind:指定要查询的标准路径类型,可以是以下字符串之一:

  • "config":配置文件的位置。通常对应于 $XDG_CONFIG_HOME/nvim$HOME/.config/nvim
  • "data":用户特定的数据文件位置。通常对应于 $XDG_DATA_HOME/nvim$HOME/.local/share/nvim
  • "cache":缓存文件的位置。通常对应于 $XDG_CACHE_HOME/nvim$HOME/.cache/nvim
  • "state":状态文件的位置(如 swap 文件、undo 文件等)。通常与 data 目录相同,但在某些情况下可能会有所不同

v:shell_error最后一条 shell 命令的结果。 非零时,表示最后一条shell 命令出错。 当为零时,表示没有问题。只有当 shell 向 Vim 返回错误代码时才会起作用。当命令无法执行时,通常使用 -1。 执行。 只读。

Vim/Neovim 使用 rtp 来定位各种类型的文件,如:

  • 插件 (plugin/*.vim)
  • 脚本 (autoload/*.vim, ftplugin/*.vim)
  • 语法定义 (syntax/*.vim)
  • 颜色方案 (colors/*.vim)
  • 文档 (doc/*.txt)
  • 其他配置文件

  • 加载顺序:当有多个相同名称的文件存在于不同的目录中时,会按照 rtp 中列出的顺序依次查找并加载第一个找到的文件。这意味着较早出现在 rtp 中的目录具有更高的优先级。

配置插件管理

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
104
105
106
107
108
109
110
111
112
-- [[ Configure and install plugins ]]
--
-- To check the current status of your plugins, run
-- :Lazy
--
-- You can press `?` in this menu for help. Use `:q` to close the window
--
-- To update plugins you can run
-- :Lazy update
--
-- NOTE: Here is where you install your plugins.
require('lazy').setup({
-- NOTE: Plugins can be added with a link (or for a github repo: 'owner/repo' link).
'tpope/vim-sleuth', -- Detect tabstop and shiftwidth automatically

-- NOTE: Plugins can also be added by using a table,
-- with the first argument being the link and the following
-- keys can be used to configure plugin behavior/loading/etc.
--
-- Use `opts = {}` to force a plugin to be loaded.
--

-- Here is a more advanced example where we pass configuration
-- options to `gitsigns.nvim`. This is equivalent to the following Lua:
-- require('gitsigns').setup({ ... })
--
-- See `:help gitsigns` to understand what the configuration keys do
{ -- Adds git related signs to the gutter, as well as utilities for managing changes
'lewis6991/gitsigns.nvim',
opts = {
signs = {
add = { text = '+' },
change = { text = '~' },
delete = { text = '_' },
topdelete = { text = '‾' },
changedelete = { text = '~' },
},
},
},

-- NOTE: Plugins can also be configured to run Lua code when they are loaded.
--
-- This is often very useful to both group configuration, as well as handle
-- lazy loading plugins that don't need to be loaded immediately at startup.
--
-- For example, in the following configuration, we use:
-- event = 'VimEnter'
--
-- which loads which-key before all the UI elements are loaded. Events can be
-- normal autocommands events (`:help autocmd-events`).
--
-- Then, because we use the `opts` key (recommended), the configuration runs
-- after the plugin has been loaded as `require(MODULE).setup(opts)`.

{ -- Useful plugin to show you pending keybinds.
'folke/which-key.nvim',
event = 'VimEnter', -- Sets the loading event to 'VimEnter'
opts = {
-- delay between pressing a key and opening which-key (milliseconds)
-- this setting is independent of vim.opt.timeoutlen
delay = 0,
icons = {
-- set icon mappings to true if you have a Nerd Font
mappings = vim.g.have_nerd_font,
-- If you are using a Nerd Font: set icons.keys to an empty table which will use the
-- default which-key.nvim defined Nerd Font icons, otherwise define a string table
keys = vim.g.have_nerd_font and {} or {
Up = '<Up> ',
Down = '<Down> ',
Left = '<Left> ',
Right = '<Right> ',
C = '<C-…> ',
M = '<M-…> ',
D = '<D-…> ',
S = '<S-…> ',
CR = '<CR> ',
Esc = '<Esc> ',
ScrollWheelDown = '<ScrollWheelDown> ',
ScrollWheelUp = '<ScrollWheelUp> ',
NL = '<NL> ',
BS = '<BS> ',
Space = '<Space> ',
Tab = '<Tab> ',
F1 = '<F1>',
F2 = '<F2>',
F3 = '<F3>',
F4 = '<F4>',
F5 = '<F5>',
F6 = '<F6>',
F7 = '<F7>',
F8 = '<F8>',
F9 = '<F9>',
F10 = '<F10>',
F11 = '<F11>',
F12 = '<F12>',
},
},

-- Document existing key chains
spec = {
{ '<leader>c', group = '[C]ode', mode = { 'n', 'x' } },
{ '<leader>d', group = '[D]ocument' },
{ '<leader>r', group = '[R]ename' },
{ '<leader>s', group = '[S]earch' },
{ '<leader>w', group = '[W]orkspace' },
{ '<leader>t', group = '[T]oggle' },
{ '<leader>h', group = 'Git [H]unk', mode = { 'n', 'v' } },
},
},
},

}}

NvChad

比kickstart更复杂一点,但也很容易上手. 其配置也是类似,首先包括全局变量、设置选项以及环境变量

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
local opt = vim.opt
local o = vim.o
local g = vim.g

-------------------------------------- options ------------------------------------------
o.laststatus = 3
o.showmode = false

o.clipboard = "unnamedplus"
o.cursorline = true
o.cursorlineopt = "number"

-- Indenting
o.expandtab = true
o.shiftwidth = 2
o.smartindent = true
o.tabstop = 2
o.softtabstop = 2

opt.fillchars = { eob = " " }
o.ignorecase = true
o.smartcase = true
o.mouse = "a"

-- Numbers
o.number = true
o.numberwidth = 2
o.ruler = false

-- disable nvim intro
opt.shortmess:append "sI"

o.signcolumn = "yes"
o.splitbelow = true
o.splitright = true
o.timeoutlen = 400
o.undofile = true

-- interval for writing swap file to disk, also used by gitsigns
o.updatetime = 250

-- go to previous/next line with h,l,left arrow and right arrow
-- when cursor reaches end/beginning of line
opt.whichwrap:append "<>[]hl"

-- disable some default providers
g.loaded_node_provider = 0
g.loaded_python3_provider = 0
g.loaded_perl_provider = 0
g.loaded_ruby_provider = 0

-- add binaries installed by mason.nvim to path
local is_windows = vim.fn.has "win32" ~= 0
local sep = is_windows and "\\" or "/"
local delim = is_windows and ";" or ":"
vim.env.PATH = table.concat({ vim.fn.stdpath "data", "mason", "bin" }, sep) .. delim .. vim.env.PATH

快捷键

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
local map = vim.keymap.set

map("i", "<C-b>", "<ESC>^i", { desc = "move beginning of line" })
map("i", "<C-e>", "<End>", { desc = "move end of line" })
map("i", "<C-h>", "<Left>", { desc = "move left" })
map("i", "<C-l>", "<Right>", { desc = "move right" })
map("i", "<C-j>", "<Down>", { desc = "move down" })
map("i", "<C-k>", "<Up>", { desc = "move up" })

map("n", "<C-h>", "<C-w>h", { desc = "switch window left" })
map("n", "<C-l>", "<C-w>l", { desc = "switch window right" })
map("n", "<C-j>", "<C-w>j", { desc = "switch window down" })
map("n", "<C-k>", "<C-w>k", { desc = "switch window up" })

map("n", "<Esc>", "<cmd>noh<CR>", { desc = "general clear highlights" })

map("n", "<C-s>", "<cmd>w<CR>", { desc = "general save file" })
map("n", "<C-c>", "<cmd>%y+<CR>", { desc = "general copy whole file" })

map("n", "<leader>n", "<cmd>set nu!<CR>", { desc = "toggle line number" })
map("n", "<leader>rn", "<cmd>set rnu!<CR>", { desc = "toggle relative number" })
map("n", "<leader>ch", "<cmd>NvCheatsheet<CR>", { desc = "toggle nvcheatsheet" })

map("n", "<leader>fm", function()
require("conform").format { lsp_fallback = true }
end, { desc = "general format file" })

-- global lsp mappings
map("n", "<leader>ds", vim.diagnostic.setloclist, { desc = "LSP diagnostic loclist" })

-- tabufline
map("n", "<leader>b", "<cmd>enew<CR>", { desc = "buffer new" })

map("n", "<tab>", function()
require("nvchad.tabufline").next()
end, { desc = "buffer goto next" })

map("n", "<S-tab>", function()
require("nvchad.tabufline").prev()
end, { desc = "buffer goto prev" })

map("n", "<leader>x", function()
require("nvchad.tabufline").close_buffer()
end, { desc = "buffer close" })

-- Comment
map("n", "<leader>/", "gcc", { desc = "toggle comment", remap = true })
map("v", "<leader>/", "gc", { desc = "toggle comment", remap = true })

-- nvimtree
map("n", "<C-n>", "<cmd>NvimTreeToggle<CR>", { desc = "nvimtree toggle window" })
map("n", "<leader>e", "<cmd>NvimTreeFocus<CR>", { desc = "nvimtree focus window" })

-- telescope
map("n", "<leader>fw", "<cmd>Telescope live_grep<CR>", { desc = "telescope live grep" })
map("n", "<leader>fb", "<cmd>Telescope buffers<CR>", { desc = "telescope find buffers" })
map("n", "<leader>fh", "<cmd>Telescope help_tags<CR>", { desc = "telescope help page" })
map("n", "<leader>ma", "<cmd>Telescope marks<CR>", { desc = "telescope find marks" })
map("n", "<leader>fo", "<cmd>Telescope oldfiles<CR>", { desc = "telescope find oldfiles" })
map("n", "<leader>fz", "<cmd>Telescope current_buffer_fuzzy_find<CR>", { desc = "telescope find in current buffer" })
map("n", "<leader>cm", "<cmd>Telescope git_commits<CR>", { desc = "telescope git commits" })
map("n", "<leader>gt", "<cmd>Telescope git_status<CR>", { desc = "telescope git status" })
map("n", "<leader>pt", "<cmd>Telescope terms<CR>", { desc = "telescope pick hidden term" })

map("n", "<leader>th", function()
require("nvchad.themes").open()
end, { desc = "telescope nvchad themes" })

map("n", "<leader>ff", "<cmd>Telescope find_files<cr>", { desc = "telescope find files" })
map(
"n",
"<leader>fa",
"<cmd>Telescope find_files follow=true no_ignore=true hidden=true<CR>",
{ desc = "telescope find all files" }
)

-- terminal
map("t", "<C-x>", "<C-\\><C-N>", { desc = "terminal escape terminal mode" })

autocmd

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
local autocmd = vim.api.nvim_create_autocmd

-- user event that loads after UIEnter + only if file buf is there
autocmd({ "UIEnter", "BufReadPost", "BufNewFile" }, {
group = vim.api.nvim_create_augroup("NvFilePost", { clear = true }),
callback = function(args)
local file = vim.api.nvim_buf_get_name(args.buf)
local buftype = vim.api.nvim_get_option_value("buftype", { buf = args.buf })

if not vim.g.ui_entered and args.event == "UIEnter" then
vim.g.ui_entered = true
end

if file ~= "" and buftype ~= "nofile" and vim.g.ui_entered then
vim.api.nvim_exec_autocmds("User", { pattern = "FilePost", modeline = false })
vim.api.nvim_del_augroup_by_name "NvFilePost"

vim.schedule(function()
vim.api.nvim_exec_autocmds("FileType", {})

if vim.g.editorconfig then
require("editorconfig").config(args.buf)
end
end)
end
end,
})

LazyVim

neovim的插件编写How to write a neovim plugin in lua与使用lazyvim不同但有相似点

1
2
3
4
5
6
├── LICENSE
├── plugin
│ └── plugin-file.lua
├── lua
│ └── main-file.lua
└── README.md

plugin和 lua 文件夹是特例,其含义如下:

  • plugin 文件夹 该文件夹中的所有文件将在 Neovim 启动时立即执行,如果想设置keymap或autocmd而不管用户是否需要该插件
  • lua 文件夹 在大多数情况下,lua 文件夹是您的插件代码所在的文件夹,只有在用户明确需要您的插件时才会执行这些代码,例如 require(‘scratch-buffer’)

插件代码该放哪

对于普通neovim插件,可以放在lua文件夹下,通过neovim加载配置路径中的init.lua通过require加载

而在lazyvim中,添加spec.

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
require("lazy").setup({
spec = {
-- add lazyvim and import its plugins
{ "lazyvim/lazyvim", import = "lazyvim.plugins" },
{ "coderunner", dir = "~/appdata/local/nvim/lua/plugins/coderunner", dev = true },
-- import/override with your plugins
{ import = "plugins" },
{ import = "lazyvim.plugins.extras.linting.eslint" },
{ import = "lazyvim.plugins.extras.formatting.prettier" },
},
defaults = {
-- by default, only lazyvim plugins will be lazy-loaded. your custom plugins will load during startup.
-- if you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default.
lazy = false,
-- it's recommended to leave version=false for now, since a lot the plugin that support versioning,
-- have outdated releases, which may break your neovim install.
version = false, -- always use the latest git commit
-- version = "*", -- try installing the latest stable version for plugins that support semver
},
install = { colorscheme = { "tokyonight", "habamax" } },
checker = {
enabled = true, -- check for plugin updates periodically
notify = false, -- notify on update
}, -- automatically check for plugin updates
performance = {
rtp = {
-- disable some rtp plugins
disabled_plugins = {
"gzip",
-- "matchit",
-- "matchparen",
-- "netrwplugin",
"tarplugin",
"tohtml",
"tutor",
"zipplugin",
},
},
},
})

重新启动 Neovim 或运行 :Lazy sync 来加载插件.有了这种设置,您就可以轻松迭代您的插件: 1. 更改插件代码。 2. 保存文件。 3. 在 Neovim 中运行 :Lazy reload plugin-name 重新加载插件。 4. 运行 :HelloWorld 或使用关键映射测试更改。 此工作流程可实现快速开发和测试,而无需不断重启 Neovim 或手动获取文件。

可以直接使用的nvim配置,lazyvim会自动加载预定义的autocmd,keymaps和options.🔌 Plugin Spec | lazy.nvim

1
2
3
4
5
6
7
8
9
10
11
12
~/.config/nvim
├── lua
│ ├── config
│ │ ├── autocmds.lua
│ │ ├── keymaps.lua
│ │ ├── lazy.lua
│ │ └── options.lua
│ └── plugins
│ ├── spec1.lua
│ ├── **
│ └── spec2.lua
└── init.lua

写本地插件时直接在plugins目录中即可. 添加插件时可用的选项

Defaults merging rules:

  • cmd:命令列表将使用您的自定义命令进行扩展
  • event:事件列表将使用您的自定义事件进行扩展
  • FT:文件类型列表将扩展为自定义文件类型
  • keys:键盘映射列表将使用您的自定义键盘映射进行扩展
  • opts:自定义 opts 将与默认 opts 合并
  • dependencies:依赖项列表将使用您的自定义依赖项进行扩展
  • 任何其他属性都将覆盖默认值

对于 fteventkeyscmdopts 您还可以指定一个 values 函数,该函数可以更改默认值,或返回要使用的新值.除此之外,还有init,config函数. Lua 插件遵循一个常见的约定,它们有一个名为 setup 的函数暴露的 Lua 模块。所以当使用 opts 时,是在告诉 lazy.nvim 该插件遵循该约定。因此,lazy.nvim 会将那个 opts 属性传递给插件的 setup 函数。

使用lazyvim写插件的debug方式🚀 Usage | lazy.nvim

小案例 插件重载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- init.lua
local utils = require("utils")
local M = {}
function showPlugins()
local plugins = require("lazy").plugins()
utils.createwin(plugins)
end
function M.setup(opts)
vim.api.nvim_create_user_command("Reload", showPlugins, {
desc = "Reload plugins",
})
end

return M
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
-- utils.lua
local M = {}
function M.createwin(items)
local width = 40
local height = #items + 2
local row = math.floor(vim.o.lines / 2) - math.floor(height / 2)
local col = math.floor(vim.o.columns / 2) - math.floor(width / 2)
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_set_option_value("bufhidden", "wipe", { buf = bufnr })
for i, item in ipairs(items) do
vim.api.nvim_buf_set_lines(bufnr, i - 1, i, false, { item.name })
end
vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr })
vim.api.nvim_set_option_value("readonly", true, { buf = bufnr })

local win_id = vim.api.nvim_open_win(bufnr, true, {
relative = "editor",
width = width,
height = height,
row = row,
col = col,
style = "minimal",
border = "rounded",
})
vim.api.nvim_set_option_value("winhl", "Normal:Normal,FloatBorder:FloatBorder", { win = win_id })

local current_line = 1
local ns_id = vim.api.nvim_create_namespace("highlight_current_line")
local function highlight_and_move_cursor()
vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1)
if current_line >= 1 and current_line <= #items then
vim.api.nvim_buf_add_highlight(bufnr, ns_id, "Visual", current_line - 1, 0, -1)
vim.api.nvim_win_set_cursor(win_id, { current_line, 0 })
end
end
highlight_and_move_cursor()
local function handle_keys(key)
if key == "k" or key == "<Up>" then
if current_line > 1 then
current_line = current_line - 1
end
elseif key == "j" or key == "<Down>" then
if current_line < #items then
current_line = current_line + 1
end
elseif key == "CR" then
local plugin_name = items[current_line].name
print("reload plugin:", plugin_name)
require("lazy.core.loader").reload(plugin_name)
-- 关闭窗口
vim.api.nvim_win_close(win_id, true)
return
end
highlight_and_move_cursor()
end

vim.api.nvim_create_autocmd({ "CursorMoved" }, {
buffer = bufnr,
desc = "highlight the line which cursor is on",
callback = function()
local cur_line = vim.api.nvim_win_get_cursor(win_id)[1]
vim.api.nvim_buf_add_highlight(bufnr, ns_id, "Visual", cur_line - 1, 0, -1)
current_line = cur_line
end,
})

-- 辅助函数用于设置按键映射
local function set_keymap(key, action)
vim.keymap.set("n", key, function()
handle_keys(action)
end, {
buffer = bufnr,
noremap = true,
silent = true,
})
end
-- 设置按键映射
set_keymap("k", "k")
set_keymap("j", "j")
set_keymap("<Up>", "Up")
set_keymap("<Down>", "Down")
set_keymap("<CR>", "CR")
-- 允许用户通过 q 键关闭窗口,仅限于当前缓冲区
vim.keymap.set("n", "q", "<Cmd>close<CR>", { buffer = bufnr, noremap = true, silent = true })
end

return M
-------------本文结束感谢您的阅读-------------
感谢阅读.

欢迎关注我的其它发布渠道