neovim 悬浮窗口介绍
前言
时间来到2017年4月30号,一名叫做bfredl的网友向neovim仓库提交了一个pr
[RFC] Floating windows in TUI and Remote UI by bfredl · Pull Request #6619 · neovim/neovim
这个pr实现了不少ide都有的一个功能那就是悬浮窗口,就如文章封面图的效果,看到这个效果你甚至可以联想到未来是不是能实现类似visual studio或者eclipse类似的效果:
时隔将近两年,这个pr终于被合并了。
如何判断自己的正在用的neovim是否支持这个特性呢,打开neovim,然后输入命令
:h nvim_open_win
:h nvim_win_set_config
如果能顺利找到这两个函数的help文档,那么恭喜你,你的neovim已经支持这个特性了,如果没有那么你最好在网上找找如何安装最新neovim的方式。
毫无疑问,上面这两个函数就是悬浮窗口的核心函数,所谓授人以鱼不如授人以渔,所以这篇个文章不是向小白们show这个特性多么牛逼,而是教你如何为自己的neovim创建悬浮窗口。利用这个特性,你可以创造很多有趣的功能,文章的最后也会收集列出一些基于这个特性的neovim插件。
介绍
关于介绍悬浮窗口的入口文档,可以由下面的命令进入:
:h api-floatwin
悬浮窗口和普通vim窗口基本一样,你可以在悬浮窗口上编辑,所支持的选项和普通窗口几乎一样。
悬浮窗口由nvim_open_win()函数创建,或者我们可以使用nvim_win_set_config()函数将一个普通窗口转化成悬浮窗口。
悬浮窗口的位置(坐标)可以相对于当前窗口左上角也可以基于当前光标。这个设置可以在函数nvim_open_win()中可以指定。
nvim_open_win()函数创建一个悬浮窗口的前提是,这个窗口需要由一个关联的buffer,这个buffer可以由nvim_create_buffer()函数来创建。在这个buffer里面的文本可以通过api-highlights来进行高亮。
下面是创建一个悬浮窗口的示例代码:
let buf = nvim_create_buf(v:false, v:true)
call nvim_buf_set_lines(buf, 0, -1, v:true, ["test", "text"])
let opts = {'relative': 'cursor', 'width': 10, 'height': 2, 'col': 0,
\ 'row': 1, 'anchor': 'NW'}
let win = nvim_open_win(buf, 0, opts)
" optional: change highlight, otherwise Pmenu is used
call nvim_win_set_option(win, 'winhl', 'Normal:MyHighlight')
你可以用nvim_win_close()函数来关闭悬浮窗口。
执行下面命令查看各个函数具体用法,当然下一章会根据实例来讲解。
:h nvim_open_win()
:h nvim_open_close()
:h nvim_create_buf()
动手创建一个你自己的悬浮窗口
悬浮窗口这个特性出来之后,我立刻想到的idea是操作系统的通知中心,从桌面右上弹出通知消息,是不是很酷?
现在就动手实现这个功能,首先本人的配置,经常会需要打印一些信息到cmd窗口中,比如当前路径,异步命令的执行状态等等,现在我们的目标就是把这些信息通过悬浮窗口显示出来,而且是从右上出来的。
后期,我们可以通过悬浮窗口,弹出任何我们需要的消息,比如整点报时,天气预报等。。当然这些还需要利用到neovim的异步特性,python接口等。这里就不细讲。
先看下效果
let s:win_list=[]
let s:global_echo_str=[]
function! NvimCloseWin(timer) abort
call timer_info(a:timer)
let l:flag=0
try
call nvim_win_close(s:win_list[0], v:true)
catch
call remove(s:win_list, 0)
let l:flag=1
endtry
if !empty(s:win_list) && l:flag == 0
call remove(s:win_list, 0)
endif
endfunction
"echo warning messag
"a:1-->err or warn or info,default is warn
"a:2-->flag of VimEnter,0 or 1
function! te#utils#EchoWarning(str,...) abort
let l:level='WarningMsg'
let l:prompt='warn'
let l:flag=0
if a:0 != 0
for s:needle in a:000
if type(s:needle) == g:t_string
let l:prompt = s:needle
if s:needle ==? 'err'
let l:level='ErrorMsg'
elseif s:needle ==? 'warn'
let l:level='WarningMsg'
elseif s:needle ==? 'info'
let l:level='None'
endif
elseif type(s:needle) == g:t_number
let l:flag=s:needle
endif
endfor
endif
if l:flag != 0 || has('vim_starting')
call add(s:global_echo_str, a:str)
return
endif
if te#env#IsNvim() && exists('*nvim_open_win') && exists('*nvim_win_set_config')
let l:str='['.l:prompt.'] '.a:str
let l:bufnr = nvim_create_buf(v:false, v:false)
let l:opts = {'relative': 'editor', 'width': strlen(l:str)+3, 'height': 1, 'col': &columns,
\ 'row': 3+len(s:win_list), 'anchor': 'NW'}
let l:win=nvim_open_win(l:bufnr, v:false,l:opts)
call nvim_buf_set_lines(l:bufnr, 0, -1, v:false, [l:str])
hi def NvimFloatingWindow term=None guifg=black guibg=#f94e3e ctermfg=black ctermbg=210
call nvim_win_set_option(l:win, 'winhl', 'Normal:NvimFloatingWindow')
call nvim_win_set_option(l:win, 'number', v:false)
call nvim_win_set_option(l:win, 'relativenumber', v:false)
call nvim_buf_set_option(l:bufnr, 'buftype', 'nofile')
call nvim_buf_set_option(l:bufnr, 'bufhidden', 'wipe')
call nvim_buf_set_option(l:bufnr, 'modified', v:false)
call nvim_buf_set_option(l:bufnr, 'buflisted', v:false)
call add(s:win_list, l:win)
call timer_start(5000, 'NvimCloseWin', {'repeat': 1})
else
redraw!
execut 'echohl '.l:level | echom '['.l:prompt.'] '.a:str | echohl None
endif
endfunction
核心代码如上,用户只要像下面这样调用就能调用悬浮窗口显示信息了:
:call te#utils#EchoWarning("Please install yapf or autopep8")
开始讲解代码,悬浮窗口的代码从nvim_create_buf函数开始,它创建了一个buffer,而nvim_open_win创建一个悬浮窗口并关联到这个buffer上面,其中l:opts是悬浮窗口的选项,选项各个含义如下:
- `relative` :如果设置那么窗口将变成悬浮窗口. 窗口的位置相对下面三个变量,
- "editor" 以编辑器窗口作为坐标系
- "win" a window. 以指定neovim窗口作为坐标系,默认当前窗口
- "cursor" :以当前光标作为坐标系
- `win` : 当 relative='win'时,对应的windows ID
- `anchor` : 定义悬浮窗口的行列的锚
- "NW" 西北 (默认)
- "NE" 东北
- "SW" 西南
- "SE" 东南
- `height` : window的高 (单位字符).最小是1
- `width` : window 宽 (单位字符)。最小是2
- `row` : 行坐标,单位和选项lines一致。可以是浮点型。
- `col` : 列坐标. 单位和选项columns一致。可以是浮点型。
- `focusable` : 悬浮窗口是否可以聚焦。是否可以通过wincmd来切换
- `external` : 指定外部gui窗口,当前不用这个选项。
值得注意的是,选项中height和width的设置比较零活的,既要考虑窗口布局,也要考虑多个消息之间不要重叠在一起。
nvim_open_win的第一个选项是buffer的handle,第二个选项是创建之后是否切换光标到悬浮窗口中,这里当然不要,所以选择v:false
nvim_buf_set_lines函数这里的作用就是将指定的字符串写到刚才申请的buffer里面,这里的字符串就是我们要显示的消息啦。
悬浮窗口的option默认是继承当前vim配置的,比如显示行数之类的,很明显我们不要显示行数,我们也希望这个悬浮窗口里面的buffer是不可编辑以及不可见的,这里就需要用到两个函数来设置选项
下面两个选项分别用于设置指定窗口和指定buffer的选项,如果某个选项是buffer指定的,则切记要用nvim_buf_set_option而不是nvim_win_set_option。
:h nvim_win_set_option
:h nvim_buf_set_option
在设置的选项中由一个比较特殊的,用于设置悬浮窗口的语法高亮,这里设置的是neovim的winhl选项,后面Normal:NvimFloatingWindow,给这个悬浮窗口指定了一个自定义高亮组,这个高亮组如下定义:
hi def NvimFloatingWindow term=None guifg=black guibg=#f94e3e ctermfg=black ctermbg=210
显示完窗口之后,就要考虑什么时候关闭窗口,这个我们自然能想到的是,定时一段时间然后自动关闭。
这里用到neovim的另外一个特性就是定时器,用法很简单,一个函数创建一个定时器,时间一到自动调用回调函数,我们在timer的回调函数里面执行悬浮窗口关闭动作,如NvimCloseWin中所示。
流程就是这么简单,当然这里涉及到一些VimL的编程基础,我这样讲,有些小白还是一脸蒙bi的,没关系,现在开始学习,过一段时间之后,你会发现很简单。
支持悬浮窗口插件&配置
下面这个就是本文章截图所示的插件,显示git commit信息。
补全框架ncm2,现在也支持通过悬浮窗口来显示doc了。
字符搜索工具flygrep可以通过悬浮窗口实时显示搜索结果
补全框架coc,现在支持通过悬浮窗口来显示补全选项,文档等。
vim-which-key:vim版本的leader guide,现在支持通过悬浮窗口显示按键导航了。
查看和搜索LSP符合,支持悬浮窗口。
悬浮时钟:
本人配置: