asyncrun.vim 是个发布于今六年前的老插件了,常常被大家用来在 quickfix 中异步运行 shell 命令,当时 vim 还是 7.4.1829 ,+job
特性刚刚能用,因为做这个插件给 bram 提了十几个关于 +job
和 quickfix 的 issue ,都被他快速修正了,并在 8.1 时变得稳定可用。
应该说这个插件是伴随 vim 8.0 一路成长稳定起来的,在修改了多年 bug 的同时,也通过收集到的有价值的反馈来不断的打磨自己。乘着新年之际,给大家分享一些该插件最近两年的主要更新,或许能够对大家提高工作效率有所帮助。
不论 Vim 还是 NeoVim 引入内置终端都好多年了,但仍然有一些人习惯在系统终端或者 tmux 之类的地方执行任务。asyncrun 提供了一个 User-Defined Runners 的机制,可以让你按想要的方式运行命令,并同时发布了一些预置的 runner 让你可以方便的用外置终端运行特定命令:
Runner | 描述 | 依赖 |
---|---|---|
gnome |
在新的 gnome-terminal 窗口里运行 | GNOME |
gnome_tab |
在 gnome-terminal 的新 tab 里运行 | GNOME |
tmux |
在 tmux pane 里运行 | Vimux |
xfce |
在新的 xfce 的 terminal 窗口里运行 | xfce4-terminal |
konsole |
在新的 konsole 终端里运行 | KDE |
macos |
在新的 macOS 系统 terminal 里运行 | macOS |
iterm |
在一个新的 iterm2 tab 里运行 | macOS + iTerm2 |
external |
在一个新的 cmd 窗口里运行 | Windows |
当使用 -mode=term
参数调用 asyncrun 命令时,可以用 -pos={runner}
参数来指定 runner 名字:
:AsyncRun -mode=term -pos=gnome ls -la
:AsyncRun -mode=term -pos=floaterm ls -la
:AsyncRun -mode=term -pos=tmux ls -la
当你在 GVim 里使用 gnome
, konsole
, external
或者 xfce
这些 runner 运行命令时,你将会得到同 IDE 里运行命令行程序一模一样的体验:
如果你在终端下使用 Vim ,那么一个新的 gnome-terminal 或者 iterm2 的 tab 是个很恰当的方式:
或者,在一个 tmux 的分屏里运行命令:
除去外部终端外,还有不少 runner 可以和各种著名的 Vim/NeoVim 内置终端增强插件们打交道:
Runner | 描述 | 依赖 |
---|---|---|
floaterm |
在一个新的 floaterm 窗口里 | floaterm |
floaterm_reuse |
在一个可复用的 floaterm 窗口里 | floaterm |
quickui |
在一个 quickui 的 popup 终端里 | vim-quickui |
toggleterm |
再一个 toggleterm 的窗口里 | toggleterm.nvim |
termhelp |
在 terminal-help 的窗口里(可复用) | vim-terminal-help |
使用 floaterm
这个 runner 的效果:
Terminal-help:
所有 Runner 皆可定制,可以修改或者定义新的 runner ,见项目 wiki customize runner.
另外一个比有意思的更新就是对 (neo)vim 内置终端的封装,可以用内置终端运行命令:
" run command in the internal-terminal in a new tab
:AsyncRun -mode=term -pos=tab ls -la
:AsyncRun -mode=term -pos=TAB ls -la
" open on the left/right/top/bottom side
:AsyncRun -mode=term -pos=left ls -la
:AsyncRun -mode=term -pos=right ls -la
:AsyncRun -mode=term -pos=top ls -la
:AsyncRun -mode=term -pos=bottom ls -la
你也许会奇怪,这和直接用内置的 :term xxx
运行 shell 命令有什么区别呢?为什么要用 asyncrun 来调度内置终端呢?理由很简单,便利性:
命令里支持类似 $(VIM_FILENAME)
或者 $(VIM_ROOT)
的宏变量替换:
:AsyncRun -mode=term -pos=TAB ls -la $(VIM_FILEDIR)
可以用 -cwd=
参数指定运行路径:
:AsyncRun -mode=term -pos=TAB -cwd=~/github ls -la
更容易的指定终端窗口的位置和尺寸(非 tab 模式):
:AsyncRun -mode=term -pos=bottom -rows=8 ls -la
:AsyncRun -mode=term -pos=right -cols=40 ls -la
可以用 -focus=0
参数进行无干扰模式(避免切换焦点到新的终端窗口上带来的分心):
:AsyncRun -mode=term -pos=tab -focus=0 ls -la
可以用 -close
来指定程序结束后自动关闭终端窗口:
:AsyncRun -mode=term -pos=tab -focus=0 -close ls -la
可以在任务结束时触发 vim 命令提醒你任务结束:
:AsyncRun -mode=term -post=echo\ 'notify' ls -la
还有其他一些额外好处:
$VIM_FILENAME
之类的会同时初始化。-listed=0
来避免在 buffer-list 里列入 terminal buffer 。-hidden=1
来将 terminal buffer 的 bufhidden
设置为 hide
。-reuse
来指定复用已经结束的内置终端窗口。最重要的是 vim/neovim 中用 :term xxx
传递包含多个单双引号,竖线,重定向符号等的复杂命令时,经常不能正确解析,特别是一些小众 shell 或者 windows 下面,而 AsyncRun 命令可以准确的传递参数。
在终端下使用 Vim 时,我个人最喜欢的内置终端位置是 -pos=TAB
,和小写的 -pos=tab
不同,这个方式会在当前 tabpage 的 左边 创建新 tab 运行内置终端命令。如此,在命令结束的时候,就能刚好回到我先前工作的 tabpage ,也回到我之前编辑的文档上:
除去宏变量替换外,上面大部分行为是可以用 :term
命令搭配 2-3 条其他命令来完成,但是用 asyncrun 只需要一条唯一的命令就能搞定这些琐碎的事情,并且消除了 vim 和 neovim 的体验差别。
asyncrun.vim 提供了可以用任何方式运行命令的方式,并且为他们提供了统一的封装和诸如宏变量,工作目录指定,窗口位置和焦点控制之类的增强。你可以从项目的 wiki: 命令用法说明 查看更多用法。
PS:如果你觉得为不同的项目或者文件类型建立 asyncrun 的 keymap 很麻烦,你可以试试兄弟插件 asynctasks.vim,它使用 asyncrun 作为运行命令的后端,并且给 vim 引入了类似 vscode 任务系统的机制,不管是运行外部命令还是执行内部 vim 命令,一切行为皆可以看作 “任务”,而对于同一个任务,在不同的项目内又有不同的执行方式,asynctasks.vim 可以帮你轻松的打理这些问题。
1
icySoda 2022-01-05 09:16:33 +08:00 via iPhone
这标题起得好像 6 年才更新一次似的
|
2
kindjeff 2022-01-05 09:34:13 +08:00 via Android
如果用 floatterm 等 terminal 插件的话,直接启动一个 terminal 再去敲命令不是更顺手么(另外 floatterm 和很多 shell 命令有直接的集成)
|
3
kindjeff 2022-01-05 09:34:56 +08:00 via Android
floatterm->floaterm
|
4
xuboying 2022-01-05 10:15:15 +08:00
Asyncrun 应该可以理解成 vsc 的 task 吧,或者说后者是借鉴 asyncrun ?
执行 ls 这种只是演示例子,实际上应该执行一些更复杂的命令 |
5
skywind3000 OP @kindjeff 对于偶然用的命令,终端里直接敲即可,对于重复命令,显然自动化更好,比如可以 map 到 f9 或者 f10 上,一按即可。
|
6
skywind3000 OP @xuboying asynctasks.vim 才是对标 vscode 的 task ,而且更强。
|
7
haoliang 2022-01-05 23:13:51 +08:00
对 neovim 的深度支持我比较惊讶,因为结合之前在知乎看到的楼主关于 neovim 的回答,楼主给我的感觉是对 neovim 并不看好。
完整看下来,我其实没有找到适合的使用场景。tmux (popup)、rofi 、neovim (lua 、floatwin) 已经解决我可以想见的异步任务使用场景了。 可能我这回复像是单纯在刷存在感...不过我早上就看到这帖子,脑子里搁了一天,就当提供一个没有引入该工具的路人视角吧 |
8
skywind3000 OP @haoliang 这和你开发用的语言有关,比如你如果是开机启动个浏览器,然后就一直改代码,刷新浏览器,那么确实没有用武之地,你如果开发需要频繁编译,或者执行的程序,那你一看就知道用在哪里了。
|
9
xuboying 2022-01-06 10:51:10 +08:00
趁着大佬在问个问题
我现在用 vim 比较少,主要我觉得还是我不太会用。比如我想实现 git grep 以后加入到 leadf 的 quickfixwindow , 每次调用都很慢。我是这么做的: 先在 quickui 里加一个内容,然后执行这个菜单,调用命令行,补完 git 的 grep 的关键字 我觉得不满意的地方是,调用麻烦,mapping 用完了。quickui 也不能实现调用顺序重排序。操错比较慢 命令重复执行不能重新补全,比如写错一个字需要修改一下,就得全部重写 另外 quicikui 和 leadf 都很现代化了,输入筐还是老实的命令行,体验不是特别好。 不知道大佬能不能给点建议。谢谢 call quickui#menu#install("&File", [ \ ['Asyncrun (git grep)', 'call _My_UI_asyncrun("git grep -nw --column " . expand("<cword>"))', ''], function! _My_UI_asyncrun(reply) abort let l:my_input=input("command=> ", a:reply) echo l:my_input execute 'AsyncRun -strip -raw -post=call\ _My_Update_quicklist() ' .l:my_input endfunction function! _My_Update_quicklist() abort let l:foo=getqflist() let l:bar=[] let l:skip=0 for l:aaa in l:foo if l:aaa.valid==1 let l:skip=1 break endif call add(l:bar, l:aaa.text) endfor if l:skip == 0 cgetexpr l:bar endif execute 'LeaderfQuickFix' endfunction |
10
skywind3000 OP @xuboying 输入框 quickui 已经做了一个了: https://github.com/skywind3000/vim-quickui/blob/master/MANUAL.md#inputbox
|
11
xuboying 2022-01-06 17:27:20 +08:00
@skywind3000 #10 感谢! quickui 让上了年纪人不用记太多复杂命令了,非常棒! Rebase 到最新的 tag 后能用这个 input 模块了。
|
12
zbinlin 2022-01-09 21:38:58 +08:00
可以实现 Bexec 插件那样的功能吗?
PS:好像之前问过类似的问题?😹 |
13
skywind3000 OP @zbinlin 更新一下,我给你加了一下,通过 asyncrun 的 “命令修改器” 机制来完成:
:AsyncRun -program=shebang -raw $(VIM_FILEPATH) :AsyncRun -program=shebang -mode=term -pos=top $(VIM_FILEPATH) |
14
skywind3000 OP @zbinlin 你可以定义成一个命令:
command! -nargs=0 AsyncBexec AsyncRun -program=shebang -mode=term -pos=top $(VIM_FILEPATH) 然后用 :AsyncBexec 代替你的老命了 |
15
Kaiv2 2022-01-27 11:06:35 +08:00
很好用,配合 LeaderF, floaterm 直接起飞
|
16
linuxyz 2022-06-29 13:47:50 +08:00
趕忙去 GitHub ❤👍 一個!
|