我的日常工作流程经常涉及重复运行任务,无论是构建命令、单元测试,还是其他一些脚本。我的理想工作流程是在右手边有一个终端分割,然后能够向它发送任务。
内置的终端
Neovim的内置:terminal ,让我可以轻松地在Neovim中打开一个终端作为分割,我有一些自定义的键位来跳转到它。
我没有使用<C-w>{h,j,k,l} 来导航分片,而是重新映射到<C-{h,j,k,l> ,并在终端分片上添加相同的映射,同时映射<Esc> :
tnoremap <Esc> <C-\><C-n>
tnoremap <C-h> <C-\><C-n><C-w>h
tnoremap <C-j> <C-\><C-n><C-w>j
tnoremap <C-k> <C-\><C-n><C-w>k
tnoremap <C-l> <C-\><C-n><C-w>l
我也更喜欢在进入终端分片时默认总是处于插入模式:
autocmd BufEnter * if &buftype == 'terminal' | :startinsert | endif
Neoterm插件
为了使事情更容易管理,我还使用Neoterm插件。这使我有能力运行。
:T ls
来打开一个终端,并执行ls 命令。如果你再运行这个命令,它将重新执行终端命令,但在同一个终端。你可能会开始看到这将构成我的终端设置的组成部分,我将在其中持续执行任务......
然后,我可以设置neoterm_size 和neoterm_default_mod 来定义终端的显示方式。我把它设置为屏幕宽度的30%左右,并且在右侧是垂直的:
vim.g.neoterm_size = tostring(0.3 * vim.o.columns)
vim.g.neoterm_default_mod = 'botright vertical'
这个配置是用Lua而不是Vimscript,因为我只使用Neovim。之前的配置代码将被移植,但我还没来得及做。
映射一个键来执行一个命令
我不希望每次想运行测试时都要手动运行:T my-command-here 。所以我开始在需要设置命令的时候手动设置一个快捷方式,比如运行测试:
:nnoremap <leader>e :T npm test<CR>
这很有效,但也有一些注意事项。首先,我使用Neoterm来启动多个终端(我经常在其他标签中为其他命令设置一些终端),而:T 会引用最新的终端。所以为了解决这个问题,我把命令调整为::1T ,这将针对第一个终端。
我还想让终端在每次执行命令之间被清除。我起初是通过使用clear 命令来做到这一点:
:nnoremap <leader>e :1T clear && npm test<CR>
但后来我发现了Neotree的:1Tclear 命令,它可以为我清除终端,而不需要我把clear 作为第一个任务。
我的最后一个问题是,我对手动输入这种映射感到厌烦!所以我开始着手将其自动化。所以我开始着手将其自动化......
建立任务执行命令
我想为<leader>e ,它将会有一个映射:
- 如果需要,创建一个终端,或者重新使用现有的终端。
- 提示要自动运行的命令。
- 一旦给出一个命令,就储存起来,并在以后的运行中再次使用(例如,只提示一次命令)。
- 在每次运行之间清除终端。
- 提供一个重置命令的选项。
要求(1)和(4)很简单;Neoterm的行为为我免费提供了这些。为了创建一个命令,我可以将其绑定到一个键上,我可以使用 nvim_create_user_command:
vim.api.nvim_create_user_command('TaskPersist', function(input)
-- implementation here
end, { nargs = '*' })
我已经知道,我可以通过使用Neoterm来执行一个命令(在这里存储为cmd ):
vim.api.nvim_command(":1Tclear")
vim.api.nvim_command(":1T " .. cmd)
所以现在我需要存储一个命令,并找到一种方法来设置它,如果它没有被提供。我可以通过设置一些全局变量来做到这一点,但我希望它是一个漂亮的用户界面这就是nui.nvim出现的地方。我的init.vim 文件中已经有了它,因为我使用的另一个插件依赖于它。而且它有一个Input 的组件!
我创建了一个变量,stored_task_command ,它将是这个Lua模块中的一个变量,用来追踪当前的命令。最初,它被设置为nil 。
我可以创建trigger_set_command_input 作为一个函数,它将创建并挂载一个Input 组件。当输入被提交时,我们将stored_task_command 设置为输入,并触发一个提供的回调函数。
这个代码的大部分直接来自于
nui.nvim的例子;我只是修改了文本提示和输入的宽度
local stored_task_command = nil
local trigger_set_command_input = function(callback_fn)
local input_component = Input({
position = "50%",
size = {
width = 50,
},
border = {
style = "single",
text = {
top = "Commmand to run:",
top_align = "center",
},
},
win_options = {
winhighlight = "Normal:Normal,FloatBorder:Normal",
},
}, {
prompt = "> ",
default_value = "",
on_submit = function(value)
stored_task_command = value
callback_fn();
end,
})
input_component:mount()
input_component:on(event.BufLeave, function()
input_component:unmount()
end)
end
就这样,我现在可以提供一个漂亮的用户界面来设置命令。
把这些部分组合起来
所以,最终的实现应该是:
- 使用Neoterm来创建或重新使用一个现有的终端
- 如果
stored_task_command是nil,触发Input框来设置命令。 - 在终端中运行该命令(通过Neoterm),在调用
:Tclear。
vim.api.nvim_create_user_command('TaskPersist', function(input)
local execute = function(cmd)
vim.api.nvim_command(":1Tclear")
vim.api.nvim_command(":1T " .. cmd)
end
if stored_task_command == nil then
-- Load up the Input component to get a value, then run it
trigger_set_command_input(function()
execute(stored_task_command)
end)
else
execute(stored_task_command)
end
end, { nargs = '*' })
有了这个,就可以了!我可以绑定到一个键:
vim.api.nvim_set_keymap("n", "<leader>e", ":TaskPersist<CR>",{})
而行为也如愿以偿 :)

允许随机的一次性任务
有时候,我确实需要重新使用我的任务运行终端,以快速完成一次性任务。为此,我可以让自定义的TaskPersist 命令选择性地接受一个命令,以用于这一次的运行。
:TaskPersist echo "some one off task"
这可以通过在定义一个自定义命令时读取参数来实现。
vim.api.nvim_create_user_command('TaskPersist', function(input)
local execute = function(cmd)
vim.api.nvim_command(":1Tclear")
vim.api.nvim_command(":1T " .. cmd)
end
local one_off_command = input.args
if one_off_command and string.len(one_off_command) > 0 then
execute(one_off_command)
elseif stored_task_command == nil then
trigger_set_command_input(function()
execute(stored_task_command)
end)
else
execute(stored_task_command)
end
end, { nargs = '*' })
将nargs 选项设置为* ,告诉Vim这个命令需要任意数量的参数(Vim将命令中的每个空格视为不同的参数)。这些参数会作为input.args ,如果提供了这些参数,我们就可以把它们作为命令执行。
重置命令
我还创建了另一个命令,以使我能够被重新提示存储一个新的命令:
vim.api.nvim_create_user_command('SetTaskCommand', function()
trigger_set_command_input(function ()
-- Don't need to do anything here beyond set it
end)
end, {})
就这样,它就完成了!
我希望你喜欢这个将几个Vim插件和一些Lua结合起来以实现所需工作流程的过程。这篇文章也说明了为什么我如此热衷于钻研Neovim而不是坚持使用Vim。六个月前我从未写过一行Lua,虽然我仍然是一个完全的初学者,但我认为任何有一些编程经验的人都可以掌握这种语言,而且我很喜欢通过写代码对我的编辑经验进行更精细的控制。
如果你想从这篇文章中获取任何代码,你可以在我的dotfiles中找到它,这些文件在GitHub上。