Vim 8 中的 C/C++ 编译运行:类 vscode 的任务系统

谦卑的向大家介绍我的新插件:asynctasks.vim,一套类似 vscode 的 tasks 系统,用于解决 vim 下长期没法轻松优雅的编译/运行 C/C++ 程序的问题。这个插件我去年酝酿了很长时间了,今年打算给他做一点宣传。

最近两年 Vim/NeoVim 发展非常迅速,各种:异步补全/LSP/查错,DAP 等项目相继出现,就连 vimspector 这样以前只能奢望 emacs 的项目如今都出现了。

然而 Vim 任然缺少一套优雅的通用的任务系统来加速你的内部开发循环(编辑,编译,测试)。很多人在处理这些 编译/测试/部署 类任务时,任然还在使用一些比较原始的方法,所以我创建了这个插件,将 vscode 的任务系统引入 Vim。

vscode 为每个项目的根目录下新建了一个 .vscode 目录,里面保存了一个 tasks.json 配置文件来描述针对该项目的各类:编译/运行任务。而 asynctasks.vim 采用类似的机制,在每个项目的根文件夹下面放一个 .tasks 配置文件来描述针对该项目的任务,同时维护一份 ~/.vim/tasks.ini 的全局配置,适配一些通用性很强的项目,避免每个项目重复写 tasks 配置。

说起来好像很简单?其实这是概念简单,很多好的设计从概念上来讲往往非常简单,但是用起来却十分灵活强大,这不是我设计的好,而是 vscode 的 tasks 系统设计的好,我只是大自然的搬运工,这应该是目前 Vim 下最强的构建工具,下面就试用一下:

单个文件的编译运行

我经常写一些小程序,验证一些小想法,那么在不用创建一个庞大工程的情况下,直接编译和运行单个文件就显得很有用,我们运行 :AsyncTaskEdit 命令,就能编辑当前项目或者当前目录的 .tasks 配置文件:

# 定义一个新任务
[file-build]
# 定义任务需要执行的命令,以 `$(...)` 形式出现的宏会在执行时被具体替换
command=gcc -O2 "$(VIM_FILEPATH)" -o "$(VIM_FILEDIR)/$(VIM_FILENOEXT)"
# 定义命令运行的目录
cwd=$(VIM_FILEDIR)

[file-run]
command="$(VIM_FILEDIR)/$(VIM_FILENOEXT)"
cwd=$(VIM_FILEDIR)
# 定义输出方式,在终端内运行
output=terminal

这里定义了两个任务:file-buildfile-run 在包含这个 .tasks 配置文件的目录及其子目录下面任意一个文件,都可以用:

:AsyncTask file-build
:AsyncTask file-run

两条命令来分别编译和运行他:

上图是运行 :AsyncTask file-build 的效果,默认模式下(output=quickfix),命令输出会实时显示在下方的 quickfix 窗口中,编译错误会和 errorformat 匹配并显示为高亮,方便你按回车跳转到具体错误,或者用 :cnext/:cprev 命令快速跳转错误位置。

任务中有丰富的以 $(..) 形式出现的宏,在实际执行时会被替换成具体值。能够流畅无阻碍的执行:“编辑/编译/测试” 循环,是提高你编程效率最有效的方法,所以我们把上面两个任务绑定到 F5 和 F9:

noremap <silent><f5> :AsyncTask file-run<cr>
noremap <silent><f9> :AsyncTask file-build<cr>

在你的 vimrc 中加入上面两句,就能按 F9 编译当前文件,F5 运行它了, 到这里你可能会说,这是 C/C++ 啊,如果我想运行 Python 代码怎么办呢?重新写个任务?不用那么麻烦,command 字段支持文件类型过滤:

[file-run]
command="$(VIM_FILEPATH)"
command:c,cpp="$(VIM_PATHNOEXT)"
command:go="$(VIM_PATHNOEXT)"
command:python=python "$(VIM_FILENAME)"
command:javascript=node "$(VIM_FILENAME)"
command:sh=sh "$(VIM_FILENAME)"
command:lua=lua "$(VIM_FILENAME)"
command:perl=perl "$(VIM_FILENAME)"
command:ruby=ruby "$(VIM_FILENAME)"
output=terminal
cwd=$(VIM_FILEDIR)

只需要在 command 字段后面加冒号,写明匹配的文件类型就行, 匹配不到的话就会使用最上面的默认命令来执行,注意文件名可能包含空格,所以要双引号,最后加了个 -save=2 可以在运行前保存所有改动的文件。

这样简单配置一下,你就能统一的用 F5 运行所有类型的文件了,这下你可以立马把 quickrun 这样的插件卸载掉了,它做的事情还没有上面这几行做的漂亮。接下来我们继续配置 F9 ,根据文件类型调用编译器:

[file-build]
command:c,cpp=gcc -O2 -Wall "$(VIM_FILEPATH)" -o "$(VIM_PATHNOEXT)" -lstdc++ -lm -msse3
command:go=go build -o "$(VIM_PATHNOEXT)" "$(VIM_FILEPATH)"
command:make=make -f "$(VIM_FILEPATH)"
output=quickfix
cwd=$(VIM_FILEDIR)
save=2

这适配了三种类型的文件,C/C++,Go,以及 Makefile,按下 F9 就可以根据当前文件类型执行对应的构建命令,并且把输出显示到 quickfix 窗口中,进行错误匹配。

上面的配置你既可以放在某个目录下,作用于所有下级目录也可以放到全局配置中,整个系统起作用。比你配置什么 makeprg 或者 vimscript 写一大堆乱七八糟的 if else 文件类型判断,和 asyncrun/neomake 调用优雅很多。

这里我们看到编译类项目一般配置 output=quickfix (默认值,不写也一样)这样可以将编译输出显示到 quickfix 窗口进行匹配,而运行类项目一般设置 output=terminal 选择终端模式,终端模式下有很多不同的运行方式,比如:内置终端,外置终端,quickfix模拟终端,经典 ! 指令,tmux 分屏等,后面会说怎么指定 output=terminal 时的运行方式。

整个项目的编译运行

仅有单个文件的编译运行是不够的,大部分时候我们是工作在一个个项目中,很多 vim 插件解决单个文件编译运行还行,但是项目级别的编译运行就相形见拙了。而 asynctasks.vim 在这个问题上应该是同类插件中做的最好的。

解决项目编译运行首先需要定位项目目录,在 Vim 中,众多插件也早就采用了一套叫做 rootmark 的机制, 从当前文件所在目录一直往上递归到根目录,直到发现某一级父目录中包含下列项目标识:

let g:asyncrun_rootmarks = ['.git', '.svn', '.root', '.project', '.hg']

则认为该目录是当前项目的根目录,如向上搜索到根目录都没找到任何标识,则将当前文件所在目录当作项目根目录。

如果你的项目在版本管理系统里,那么仓库的顶层文件夹就会被自动识别成项目的根目录,而如果你有一个项目既不在 git 中,又不在 svn 中怎么办?或者你的 git/svn 的单个仓库下面有很多项目,你并不想让最上层作为项目根目录的话,你只要在你想要的地方新建一个空的 .root 文件就行了。

最后一个边界情况,如果你没有打开文件(未命名新文件窗口),或者当前 buffer 是一个非文件(比如工具窗口),怎么办呢?此时会使用 vim 的当前文件夹(即 :pwd 返回的值)作为项目目录。

这基本是一套多年下来行之有效的约定了,众多插件都采用这个方法确定项目位置,比如大家熟知的:YCMAsyncRunCtrlPLeaderFcclsGutentags 等等。vscode 也采用类似的方法在项目顶层放置一个隐藏的 .vscode 文件夹,来标记项目根目录。

有了项目位置信息后我们就可以在任务中用 $(VIM_ROOT) 或者它的别名 <root> 来代替项目位置了:

[project-build]
command=make
# 设置在当前项目的根目录处运行 make
cwd=$(VIM_ROOT)

[project-run]
command=make run
# <root> 是 $(VIM_ROOT) 的别名,写起来容易些
cwd=<root>
output=terminal

我们把这两个任务分别绑定到 F6 和 F7 上面:

noremap <silent><f6> :AsyncTask project-run<cr>
noremap <silent><f7> :AsyncTask project-build<cr>

那么我们就能轻松的使用 F7 来编译当前项目,而 F6 来运行当前项目了。那么也许你会问,上面定义的都是用 make 工具的来编译运行啊,我的项目不用 make 构建怎么办?项目又不能根上面单个文件那样通过单个文件类型来区分 command,难道我要把不同构建类型的项目定义很多个不同的 task,搞一大堆类似 project-build-cmakeproject-make-ninjia ,然后在 F1-F12 上绑定满它们吗?

配置优先级

并不需要,最简单的做法是你可以把上面两个任务(project-buildproject-run)配置成公共任务,放到 ~/.vim/tasks.ini 这个公共配置里,然后对于所有一般的 make 类型项目,你就不用配置了。

而对于其他类型的项目,比如某个项目中,我还在用 msbuild 来构建,我就单独给这个项目的 .tasks 局部配置中,再定义两个名字一模一样的局部任务,比如项目 A 中:

[project-build]
command=vcvars32 > nul && msbuild build/StreamNet.vcxproj /property:Configuration=Debug /nologo /verbosity:quiet
cwd=<root>
errorformat=%f(%l):%m

[project-run]
command=build/Debug/StreamNet.exe
cwd=<root>
output=terminal

asynctasks.vim 中,局部配置的优先级高于全局配置,下层目录的配置高于上层目录的配置(.tasks 可以嵌套存在)。因此,在 A 项目中,老朋友 project-buildproject-run 两个任务被我们替换成了针对 A 项目的 msbuild 的方法。

先调用 vcvars32.bat 初始化 Visual C++ 环境,然后用 && 符号连接 msbuild 命令行,并且将 errorformat 设置成 %f(%l):%m 来适配 VC++ 输出的错误信息。

这样在 A 这个项目中,我任然可以使用 F7 来编译项目,然后 F6 来运行整个项目,不会因为项目切换而导致我的操作发生改变,我可以用统一一致的操作,处理各种不同类型的项目,这就是本地任务和全局任务协同所能产生的奇迹。

PS:可以用 :AsyncTaskEdit 来编辑本地任务,:AsyncTaskEdit! 来编辑全局任务。

可用任务查询

那么当前项目下,到底有些什么可用任务呢?他们到底是局部还是全局的?一个任务到底最终是被什么配置文件给 override 掉了?我们用 :AsyncTaskList 命令可以查看:

该命令能显示可用的 task 名称,具体命令,以及来自哪个配置文件。

PS:以点 . 开头的任务名在查询时会被隐藏,使用 :AsyncTaskList! 查看所有任务。

宏变量展开

前面任务配置里,用到了几个形状如同 $(VIM_xxx) 的宏,具体在运行时会具体替换成对应的值,常用的宏有:

$(VIM_FILEPATH)    # 当前 buffer 的文件名全路径
$(VIM_FILENAME)    # 当前 buffer 的文件名(没有前面的路径)
$(VIM_FILEDIR)     # 当前 buffer 的文件所在路径
$(VIM_FILEEXT)     # 当前 buffer 的扩展名
$(VIM_FILENOEXT)   # 当前 buffer 的主文件名(没有前面路径和后面扩展名)
$(VIM_PATHNOEXT)   # 带路径的主文件名($VIM_FILEPATH 去掉扩展名)
$(VIM_CWD)         # 当前 Vim 目录(:pwd 命令返回的)
$(VIM_RELDIR)      # 相对于当前路径的文件名
$(VIM_RELNAME)     # 相对于当前路径的文件路径
$(VIM_ROOT)        # 当前 buffer 的项目根目录
$(VIM_CWORD)       # 光标下的单词
$(VIM_CFILE)       # 光标下的文件名
$(VIM_CLINE)       # 光标停留在当前文件的多少行(行号)
$(VIM_GUI)         # 是否在 GUI 下面运行?
$(VIM_VERSION)     # Vim 版本号
$(VIM_COLUMNS)     # 当前屏幕宽度
$(VIM_LINES)       # 当前屏幕高度
$(VIM_SVRNAME)     # v:servername 的值
$(VIM_DIRNAME)     # 当前文件夹目录名,比如 vim 在 ~/github/prj1/src,那就是 src
$(VIM_PRONAME)     # 当前项目目录名,比如项目根目录在 ~/github/prj1,那就是 prj1
$(VIM_INIFILE)     # 当前任务的 ini 文件名
$(VIM_INIHOME)     # 当前任务的 ini 文件的目录(方便调用一些和配置文件位置相关的脚本)

上面这些宏基本够你日常使用了,除了替换 commandcwd 配置外,同名的环境变量也被设置成同样的值,例如你某个任务命令太复杂了,你倾向于写道一个 shell 脚本中,那么 command 配置就可以简单的调用一下脚本文件:

[project-build]
command=build/my-build-task.sh
cwd=<root>

根本不用传参,这个 my-build-task.sh 脚本本内部直接用 $VIM_FILENAME 这个环境变量就能取出文件名来,这样通过环境变量传递当前项目/文件信息的方法,结合外部脚本,能让我们定义各种相对复杂的任务,比直接裸写几行 vimscript 的 keymap 强大灵活多了。

那么当前这些宏到底会被展开成什么呢?我们可以通过 :AsyncTaskMacro 命令查看:

左边是宏名称,中间是说明,右边是具体展开值。这条命令很有用,当你写 task 配置忘记宏名称了,用它随时查看,不用翻文档。

多种运行模式

配置任务时,output 字段可以设置如何运行任务,它有下面两个值:

  • quickfix: 默认值,实时显示输出到 quickfix 窗口,并匹配 errorformat。
  • terminal:在终端内运行任务。

第一个自然没啥好说,当设置为第二个 terminal 时,还可以通过一个全局变量:

let g:asynctasks_term_pos = 'xxx'

来具体设置终端的工作位置和工作模式,它有几个可选值:

名称 类型 说明
quickfix 伪终端 默认值,使用 quickfix 窗口模拟终端,输出不匹配 errorformat
vim 传统 vim 的 ! 命令运行任务,有些人就是迷恋这种方式。
tab 内置终端 在一个新的 tab 上打开内置终端,运行程序。
TAB 内置终端 tab 但是是在左边打开,关闭后方便回到上一个 tab
top 内置终端 在上方打开可复用内部终端。
bottom 内置终端 在下方打开可复用内部终端。
left 内置终端 在左边打开可复用内置终端。
right 内置终端 在右边打开可复用内置终端。
external 外部终端 启动一个新的操作系统的外置终端窗口,运行程序。

另外在任务配置文件中,也可以用 pos=? 来强制指定该任务需要何种方式运行。

基本上 Vim 中常见的运行模式都包含了,选择一个你喜欢的模式即可,见到那演示一下:

output=terminal 时,设置:

let g:asynctasks_term_pos = 'bottom'

那么运行 :AsyncTask file-run 时,就能在下方的内置终端运行任务了:

终端窗口会复用,如果上一个任务结束了,再次运行时不会新建终端窗口,会先尝试复用老的已结束的终端窗口,找不到才会新建。当使设置为 top/bottom/left/right 时,可以用下面两个配置确定终端窗口大小:

let g:asynctasks_term_rows = 10    " 设置纵向切割时,高度为 10
let g:asynctasks_term_cols = 80    " 设置横向切割时,宽度为 80

有人说分屏的内置终端太小了,没关系,你可以设置成 tab

let g:asynctasks_term_pos = 'tab'

这样基本就能使用整个 vim 全屏大小的区域了:

整个 tab 都用于运行你的任务,应该足够大了吧?这是我比较喜欢的方式。

默认的 quickfix 方式当然可以运行你的任务,但是它不能处理用户输入,当你的程序需要和用户交互时,你可能会需要一个真实的终端。

Bonus

  • tab 模式的终端也可以复用,将 g:asynctasks_term_reuse 设置成 1 即可。
  • 如果你想在打开新分屏终端的时候保持你的焦点不改变,可以将 g:asynctasks_term_focus 设置成 0 即可。

(PS:内置终端有时候需要调教一下才会比较顺手,这里鼓励大家使用 ALT+HJKL 来进行窗口切换,淘汰老旧的 CTRL+HJKL,再使用 ALT+q 来返回终端 NORMAL 模式,这几个 keymap 我用到现在都非常顺手。)

外部终端

在 Windows 下经常使用 Visual Studio 的同学们一般会喜欢像 VS 一样,打开一个新的 cmd 窗口来运行程序,我们设置:

let g:asynctasks_term_pos = 'external'

那么对于所有 output=terminal 的任务,就能使用外部系统终端了:

是不是有点 VS 的感觉了?基本可能的运行方式都有了。

本插件基本上提供了所有 Vim 中可能的运行程序的方式了,选个你喜欢的即可。

Runner

得益于 AsyncRun 的 customizable runners 机制,任务可以按你想要的任何方式执行,插件发布包含了一批默认 runner:

Runner 描 述 需 求 链 接
gnome 在新的 Gnome 终端里运行 GNOME gnome.vim
gnome_tab 在另一个 Gnome 终端的 Tab 里运行 GNOME gnome_tab.vim
xterm 在新的 xterm 窗口内运行 xterm xterm.vim
tmux 在一个新的 tmux 的 pane 里运行 Vimux tmux.vim
floaterm 在 floaterm 的新窗口里运行 floaterm floaterm.vim
floaterm_reuse 再一个可复用的 floaterm 窗口内运行 floaterm floaterm_reuse.vim
quickui 在 quickui 的浮窗里运行 vim-quickui quickui.vim
toggleterm 使用 toggleterm 窗口运行 toggleterm.nvim toggleterm.vim
termhelp 在 terminal-help 的终端里运行 vim-terminal-help termhelp.vim
xfce 在 xfce 终端中运行 xfce4-terminal xfce.vim
konsole 在 KDE 的自带终端里运行 KDE konsole.vim
macos 在 macOS 的系统终端内运行 macos macos.vim
iterm 在 iTerm2 的 tab 中运行 macos + iTerm2 iterm.vim

当为 AsyncRun 定义了一个 runner,可以在本插件的任务配置里用 pos 配置来指定:

[file-run]
command=python "$(VIM_FILEPATH)"
cwd=$(VIM_FILEDIR)
output=terminal
pos=gnome

当你使用:

:AsyncTask file-run

这个任务将会在 gnome-terminal 的 runner 里执行:

在 gnome 下用 gvim 时,在新弹出的终端窗口里运行程序,和 IDE 里开发的体验完全一致。

如果你想避免为大部分任务设置 pos 配置,设置全局配置会方便很多:

let g:asynctasks_term_pos = 'gnome'

全局配置生效后,任何 output=terminal 的任务如果没有包含 pos 字段,都将默认用 gnome-terminal 来运行任务。

注意,任务配置里的 option 字段必须为 terminal,同时任务配置里的 pos 会比全局配置 g:asynctasks_term_pos 拥有更高的优先级。

如果你想自定义一个 runner,可以参考 asyncrun 的文档:customize-runner

高级话题

本插件 asynctasks.vim 还有很多高级的玩法,我们继续:

交互式任务

有一些任务需要用户输入点什么东西,比如你配置一个全局搜索字符串的任务,运行时如果希望用户输入关键字的话,你就会用到这项功能。

任务的 command 字段可以接受形如 $(-...) 的宏,在运行 :AsyncTask xxx 时,如果 command 里包含这些宏,则会在 Vim 里提示你输入内容:

[task1]
command=echo hello $(-name), you are a $(-gender).
output=terminal

在你使用 :AsyncTask task1 运行任务时,该任务会在 Vim 中要求你输入参数:

命令行里有两个参数需要输入,问完第一个会问第二个,按 ESC 放弃,回车确认,完成后将会把输入的值替换到上面的命令中,然后开始执行:

如上图所示,该任务正确的显示了用户输入的内容。

你也可以在 :AsyncTask {任务名} 命令的后面用 -varname=xxx 的形式显示提供值:

:AsyncTask task1 -name=Batman -gender=boy

当你在命令行里提供这些值了,AsyncTask 就不会再问你要输入了。

提示:使用 $(-prompt:default) 可以提供一个默认值,同时 $(-prompt:) 会记住上次的输入。使用 $(-gender:&male,&female) 来给用户提供备选。

真实案例(我自己用的):

[grep]
command=rg -n --no-heading --color never "$(-word)" "<root>" -tcpp -tc -tpy -tvim -tgo -tasm
cwd=$(VIM_ROOT)
errorformat=%f:%l:%m

这是我的全局 grep 任务,只要运行 :AsyncTask grep 就会提示我输入要查找的关键字,输入后就能在当前项目中搜索符合条件的代码了。

当然,这个 $(-word) 的值你也可以用命令参数提供:

:AsyncTask grep -word=hello

如果在另一个项目中我需要指明搜索更多类型的文件,我可以专门为该项目定义一个局部的 grep 任务,并用另外的参数去执行 rg

当然,大部分时候,这个全局的 grep 任务已经足够我用了,对于其他项目,rg 除了支持 .gitignore 外,还能在项目内放一个额外的 .ignore 文件,来指定需要跳过什么(比如一大堆测试文件我不想搜索),或者还要搜索什么。

内部变量

内部变量有很多种不同的用途,比如可用来管理不同的 building target。可以在配置文件的 [+] 区域定义内部变量:

[+]
build_target=build_x86
test_target=test_x86

[project-build]
command=make $(+build_target)
cwd=<root>

[project-test]
command=make $(+test_target)
cwd=<root>

command 配置项中,任何符合 $(+var_name) 的文本都会被替换成星号区域定义的内容,所以,上面 test 任务的命令最终会变成:

make build_x86

想要切换 project-build 任务的 target 的话,直接打开配置修改 [+] 区域内的变量值就行,不用每次去修改 command 配置项。

同样,内部变量的值也可以通过命令行参数,以 +varname=value 的形式传递覆盖:

:AsyncTask project-test  +test_target=mytest

Default values can be defined as $(+varname:default) form, it will be used if variables are absent in both [+] section and :AsyncTask xxx arguments.

变量可以有默认值,默认值定义为 $(+变量名:默认值)

[project-test]
command=make $(+test_target:testall)
cwd=<root>

如果该变量即没有在 [+] 区域里定义,也没有在 :AsyncTask xxx 的命令行后提供,那么默认值就会生效。

内部变量还有第三种定义方法,可以在 g:asynctasks_environ 中定义,方便 vimscript 操作:

let g:asynctasks_environ = {'foo': '100', 'bar': '200' }

由于同样的变量可以在多处定义,那么他们的优先级是:

  • 低优先级: 全局配置的 [+] 区间。
  • 中优先级:本地 .tasks 配置的 [+] 区间。
  • 高优先级:vimscript 的字典变量 g:asynctasks_environ
  • 最高优先级: 位于 :AsyncTask 任务名 命令后面的 +varname=value 参数。

高优先级定义的值会覆盖低优先级的内容,利用这个特性很多类似的任务可以只定义一遍。

比如我们在全局配置中定义了两个任务:

[file-build]
command=gcc -O2 -Wall "$(VIM_FILEPATH)" -o "$(VIM_PATHNOEXT)" $(+cflags:) 
cwd=$(VIM_FILEDIR)

[project-find]
command=rg -n --no-heading --color never "$(-word)" "<root>" $(+findargs:)
cwd=$(VIM_ROOT)
errorformat=%f:%l:%m

他们都各自引入了一个默认值为空字符的变量(cflags, findargs),如果本地项目里我们有两个同名但是参数略微不同的任务,我们不需要复制粘贴再定义一次,只需要在本地 .tasks 配置中:

[+]
clags=-g -gprof
findargs=-tcpp

这样定义一下就能获得不同的命令效果了,这个可以很方便的简化很多类似任务的定义。

不同 profile 的任务

单个任务允许具有多个不同的 profile

[task1:release]
command=gcc -O2 "$(VIM_FILEPATH)" -o "$(VIM_PATHNOEXT)"
cwd=$(VIM_FILEDIR)

[task1:debug]
command=gcc -g "$(VIM_FILEPATH)" -o "$(VIM_PATHNOEXT)"
cwd=$(VIM_FILEDIR)

这里定义了 task1 的两个不同 profile:releasedebug。默认的 profile 是 debug,可以用下面命令改为 release

:AsyncTaskProfile release

或者:

let g:asynctasks_profile = 'release'

接着,:AsyncTask task1 就能用 release 的方式运行 task1 了。

附:当 AsyncTaskProfile 命令后跟随多个参数时:

:AsyncTaskProfile debug release

会弹出一个对话框,让你选择到底是用 debug 还是用 release

命令对操作系统的适配

本插件支持为不同的操作系统定义不同的命令:

[task1]
command=echo default
command/win32=echo win32 default
command/linux=echo linux default
command:c,cpp/win32=echo c/c++ for win32
command:c,cpp/linux=echo c/c++ for linux

命令不当可以用前面提到的文件类型来过滤,还能用操作系统来过滤,如果无法匹配那么默认命令(第一个)就会被使用。

本插件仅仅会自动检测 windows 和 linux,你可以强制设置系统类型:

let g:asynctasks_system = 'macos'

这样就会匹配所有以 /macos 结尾的命令了。

任务数据源

当任务很多时,你可能需要各种 UI 插件给你提供任务选择,你可以用下面接口:

let current_tasks = asynctasks#list("")

来取得所有任务信息,它会返回一个列表,每个 item 是一个任务,方便你同各种 fuzzy finder 集成。

自定义运行方式

如果你还想在 tmux 的 split 里或者 gnome-terminal 的 window/tab 里运行任务,以及自定义更多的运行模式,见 customize runners.

插件设置

有很多设置可以具体控制本插件的行为:

The g:asynctasks_config_name option

修改默认 .tasks 配置文件的名称,不喜欢的话可以随便改成:

let g:asynctasks_config_name = '.asynctask'
let g:asynctasks_config_name = '.git/tasks.ini'

如果你多个本地配置文件,可以用逗号分隔不同配置名字,或者直接用列表:

let g:asynctasks_config_name = '.tasks,.git/tasks.ini,.svn/tasks.ini'
let g:asynctasks_config_name = ['.tasks', '.git/tasks.ini', '.svn/tasks.ini']
The g:asynctasks_rtp_config option

修改 ~/.vim 下面的全局配置文件 tasks.ini 的名称:

let g:asynctasks_rtp_config = "asynctasks.ini"
The g:asynctasks_extra_config option

额外全局配置,除了 ~/.vim/tasks.ini 外,你还可以指定更多全局配置:

let g:asynctasks_extra_config = [
    \ '~/github/my_dotfiles/my_tasks.ini',
    \ '~/.config/tasks/local_tasks.ini',
    \ ]

他们会在加载完 ~/.vim/tasks.ini 后马上加载。

The g:asynctasks_term_pos option

你想要何种命令运行 output=terminal 的任务,具体见 多种运行模式.

The g:asynctasks_term_cols option

内置终端的宽度(使用水平分割时)。

The g:asynctasks_term_rows option

内置终端的高度(使用垂直分割时)。

The g:asynctasks_term_focus option

设置成 0 可以在使用分屏内置终端的时候,避免焦点切换。

The g:asynctasks_term_reuse option

设置成 1 可以复用 tab 类型的内置终端。

The g:asynctasks_term_hidden option

设置成 1 的话,所有内置终端的 buffer 会将 bufhidden 初始化成 hide。那么不管你全局有没有设置 hidden,该终端窗口都变成可以隐藏的。

The g:asynctasks_template option

设置成 0 的话,新建配置文件时就不使用模板了。

The g:asynctasks_confirm option

设置成 0 的话,使用 :AsyncTaskEdit 时就不需要你确认文件名了。

The g:asynctasks_filetype option

任务配置文件的 filetype,默认值是 “taskini”.

使用案例

这里有很多实际使用案例:

命令行工具

本插件提供一个名为 asynctask.py 的脚本 (在 bin 文件夹内),可帮你在 shell 中运行任务:


# 在你项目的任意一个子目录中运行任务
# 不需要 cd 回到项目根目录,因为任务中有过 '-cwd=<root>' 的配置
$ asynctask project-build

# 编译文件
$ asynctask file-build hello.c

# 运行文件
$ asynctask file-run hello.c

使用 fzf 来选择任务:

更多内容,请访问:

Loading

About skywind

Putty 本无树,MinGW 亦非台
This entry was posted in 随笔 and tagged . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *