星期六, 二月 28, 2026

VIM学习笔记 标签页面板(Tabpanel)

patch 9.1.1391中,Vim引入了新的UI元素——标签页面板(tabpanel),用于在窗口垂直侧边栏上显示标签页。

显示/隐藏标签页面板

通过设置showtabpanel选项,可以选择显示或隐藏标签页面板。默认情况下,showtabpanel缺省值为0,将不显示标签页面板。

使用以下命令,将始终显示标签页面板:

:set showtabpanel=2

使用以下命令,将在至少有两个标签页时才会显示标签页面板:

:set showtabpanel=1

使用以下命令,将关闭标签页面板:

:set showtabpanel=0

标签页面板选项

通过设置tabpanelopt选项,可以定制标签页面板:

  • align,决定标签页面板显示在窗口的左侧或右侧;
  • columns,指定标签页面板的宽度;
  • vert,指定标签页面板和窗口显示部分间是否显示垂直分割符。

使用以下命令,将在屏幕左侧显示宽度为15的标签页面板:

:set tabpanelopt=vert,align:left,columns:15

通过设置fillchars选项,可以定义标签页面板的垂直分隔符:

:set fillchars+=tpl_vert:\|

标签面板配色方案

默认情况下,gVim使用GUI风格的配色方案:

guioptions+e

使用以下命令,可以设置gVim的标签页(tabline)和标签面板(tabpanel)都使用现有的配色方案

:set guioptions-=e

guioptions-e

将以下代码添加到vimrc启动文件中,可以进一步定制标签页和标签面板的配色:

hi TabLine      guifg=#c5c8c6 guibg=#303030 gui=none ctermfg=254 ctermbg=236 cterm=none
hi TabLineSel   guifg=#c5c8c6 guibg=#1d1f21 gui=bold ctermfg=254 ctermbg=236 cterm=bold
hi TabLineFill  guifg=#c5c8c6 guibg=#303030 gui=none ctermfg=254 ctermbg=236 cterm=none
hi VertSplit	guifg=#3a3a3a guibg=#1d1f21 gui=none ctermfg=254 ctermbg=236 cterm=none

由于Tabpanel相关高亮组已经默认链接至Tabline,所以只需要定义Tabline相关高亮组即可:

tabpanel_highlight_group_link

以下为标签页面板的显示效果:

tabpanel

使用:help tabpanel命令,可以查看更多帮助信息。

Ver: 260228 | YYQ<上一篇 | 目录 下一篇>

星期六, 一月 31, 2026

VIM学习笔记 编辑CSV数据文件

CSV是一种以逗号分隔值的文本文件,常用于电子表格程序,如 Microsoft Excel 或 OpenOffice Calc。CSV 文件中的每一行代表数据库中的一行,而每行数据又由一个或多个以逗号分隔的字段组成。

针对典型的CSV数据处理场景,以下将结合Vim内置命令、操作系统命令和csv.vim插件进行操作演示。

您也可以使用Rainbow CSV等其他插件执行类似操作。

安装配置

推荐使用minpac等插件管理器,来安装GitHub上的csv.vim插件。(请参阅官方安装文档

固定标题行

通过以下步骤,将文件拆分在两个水平窗口中显示并绑定滚动,可以保持标题行始终置顶显示:

  1. 打开CSV文件,使用:set nowrap命令,取消折行显示;
  2. 使用:sp命令,水平分割窗口,然后点击1Ctrl-w_快捷键,调整窗口尺寸仅显示首行;
  3. 点击Ctrl-wj快捷键,跳转到下一窗口;
  4. 使用:set scrollopt=hor命令,显示横向滚动条;使用:set scrollbind命令,绑定滚动;
  5. 点击Ctrl-wk快捷键,跳转到上一窗口;
  6. 使用:set scrollbind命令,绑定滚动;

此时在文件中左右移动,标题行与内容将自动同步对齐:

CSV-Frozen-Headers.png

使用csv.vim插件,执行以下命令可以固定标题行:

:Header

执行以下命令可以取消固定标题行:

:Header!

按列对齐

假设有以下逗号分割的CSV文件,如下图红线所示,数据列没有对齐:

CSV-Column-Align-Before

在Linux中,使用过滤器(Filter)执行外部column命令来按列对齐:

:%!column -t

CSV-Column-Align-After

使用csv.vim插件,执行以下命令可以按列对齐:

:%ArrangeColumn

plugin_csv_ArrangeColumn

按列排序

调用外部sort命令,可以按照第2列进行排序:

:%!sort -t ',' -k2

使用csv.vim插件,执行以下命令可以按照第2列进行排序:

:Sort 2

按列合并

假设names.txt文件,包含姓名信息:

Jason
Cameron
Tim

假设numbers.txt文件,包含电话信息:

555-1234
555-9876
555-2468

使用以下Linux命令,可以将多个文件按列合并:

$ paste names.txt numbers.txt

CSV-Column-Merge-paste

按行合并

在Windows中,使用copy命令可以合并多个文件:

$ copy *.csv merged-filname.csv

在Linux中,使用cat命令可以合并多个文件:

$ cat *.csv > merged-file.csv

拆分文件

调用外部split命令,可以按照制定行数拆分成多个文件:

$ split -l200 passengers.csv --verbose

creating file ‘xaa’
creating file ‘xab’
creating file ‘xac’
creating file ‘xad’
creating file ‘xae’

调用外部 wc.命令,可以统计文件包含的行数,以验证拆分的结果:

$ wc -l xa*

200 xaa
200 xab
200 xac
200 xad
 92 xae
892 total

删除空行

使用以下全局命令,可以删除所有空行:

:%g/^ *$/d

在Linux中,使用以下任一命令都可以删除所有空行:

$ sed '/^$/d' old.csv > new.csv

$ grep . old.csv > new.csv

$ awk NF old.csv > new.csv

$ cat old.csv | tr -s '\n' new.csv

删除重复行

使用sort命令,将删除重复行并对文本进行排序(请注意,这将改变文本行的原有顺序):

:sort u

使用以下全局命令,可以删除所有重复行:

:g/^\(.*\)$\n\1$/d

在Linux中,使用过滤器(Filter)执行外部uniq命令可以删除所有重复行:

:%!uniq

数据统计

使用以下命令,可以查找并计数行内的逗号数量

:s/,/,/gn

使用csv.vim插件,执行以下命令可以显示当前行的列数:

:NrColumns!

Ver: 260128 | YYQ<上一篇 | 目录 下一篇>

VIM学习笔记 Rainbow CSV数据编辑

使用Rainbow CSV插件,可以为编辑CSV数据文件提供语法高亮、按列对齐和复杂查询等高级功能。

安装配置

推荐使用minpac等插件管理器,来安装GitHub上的Rainbow CSV插件。(请参阅官方安装文档

多重高亮显示

安装Rainbow CSV插件之后,打开CSV文件将自动以不同的颜色显示不同的列,以提高数据的可读性:

rainbow_csv_color

按列对齐

使用以下命令,可以将数据按列对齐:

:RainbowAlign

rainbow_csv_align

请注意,插件是通过新增空格来实现按列对齐,如果您此时保存文件,那么新增的空格也将被保存到原文件中。

使用以下命令,可以删除由:RainbowAlign命令新增的空格,以恢复文件原貌:

:RainbowShrink

数据查询

使用类似SQL的RBQL查询语言,可以对CSV文件进行灵活的数据处理,甚至包括连接查询和更新。

使用以下命令,将按照第8列的倒序,显示第1列和第8列:

Select a1, a8 order by a8 desc

如下图所示,随着光标移动,可以在状态上查看当前列的序号和名称:

rainbow_csv_column_info

使用以下命令,可以将查询结果复制回原文件并覆盖原有内容:

:RainbowCopyBack

关于RBQL查询语言的更多信息,可以查看在线文档

Ver: 251013 | YYQ<上一篇 | 目录 下一篇>

星期六, 七月 26, 2025

VIM学习笔记 网页验证(W3C Validation)

符合标准的网页开发,可谓是不作恶的底线了。通过生产具有可读性的高质量代码,促成在不同浏览器中达成一致的显示效果,并且关照不同用户群体的可访问性(accessibility)。

可以使用W3C Markup Validation Service,在线验证网页是否符合标准;也可以使用Html Validator插件,在浏览器中验证网页。而更高效的方式,则是使用Nu Html Checker (v.Nu)开源工具,它可以在命令行中离线批量验证多个文件。

vnu_github

安装运行

可以直接从GitHub下载各平台下的可运行程序。

如果您已经安装了Java运行环境,那么可以下载 vnu.jar文件直接运行。

如果您没有安装过Java运行环境,那么可以下载相应操作系统的预编译包,比如vnu.windows.zip,其中内置了的Java系统。

将vnu所在目录,增加到path环境变量中:

vnu_windows_path

在命令行中,执行以下命令,可以显示帮助信息即说明安装成功。

$ vnu --help

vnu_cmd_help

在Mac操作系统下,可以通过Homebrew进行安装:

$ brew install vnu

验证网页

使用以下命令,可以验证在线网页:

$ vnu https://yyq123.github.io/learn-vim/learn-vi-00-00-TOC.html

使用以下命令,可以验证指定文件:

$ vnu filename.html

使用以下命令,可以验证多个文件文件:

$ vnu filename1.html filename2.html

使用以下命令,可以验证当前目录下所有网页文件:

$ vnu --asciiquotes --skip-non-html .

--asciiquotes选项,指定在输出信息中使用ASCII编码的引号(替代默认的Unicode编码的引号);

--skip-non-html选项,仅验证所有扩展名为html、htm、xhtml或xht的网页文件。

默认情况下,命令按照GNU error format格式输出。也可以使用--format选项,指定、xml、json或text格式。

vnu_cmd_out

使用--errors-only选项,将仅显示错误信息,而忽略info和warning信息:

$ vnu --errors-only filename.html

在Vim中调用vnu

在Vim中,可以直接调用vnu来验证当前文件,将在弹出的操作系统命令行中显示输出信息,需要点击回车键来关闭命令行窗口并返回到Vim:

:!vnu --asciiquotes %

vnu_vim_shell

使用vnu进行编译

请将以下代码,保存至~/.vim/after/compiler/w3c-html-validator.vim

let current_compiler = "w3c-html-validator"
CompilerSet makeprg=vnu\ $*\ --asciiquotes\ '%'
CompilerSet errorformat=\"file:%f\":%[0-9]%#\.%c-%l\.%[0-9]%#:\ %m

vimrc配置文件中,指定新建的编译器:

autocmd FileType html,xhtml compiler w3c-html-validator

再次打开HTML文件,使用以下命令即可使用vnu进行验证,并在QuickFix中显示输出信息:

:make

如果已经打开了多个文件,那么使用以下命令则可以对参数(Arguments)列表中的所有文件中进行验证:

:make ##

vnu_make_quickfix

Ver: 250726 | YYQ<上一篇 | 目录 下一篇>

星期一, 七月 21, 2025

VIM学习笔记 快捷键速查表(vim-which-key)

 Vim提供了一种不依赖鼠标的快速高效的编辑体验,通过大量的键盘快捷键,可以快速地进行文本移动、编辑、修改等操作。然而如何记忆和使用种类繁多的快捷键,也形成了另一种挑战。

打开vimrc配置文件,然后使用以下命令,可以列示所有F功能键的当前定义:

:for i in range(1, 12) | execute("map <F".i.">") | endfor

map_list_FunKey

使用以下命令,可以将键盘映射(和修改过的选项),写入到当前目录下的_exrc文件:

:mk

map_list_mk_exrc

更高效的方式,则是使用vim-which-key插件,可以在弹出窗口(popup)中列示当前所有快捷键的定义,大大减轻了记忆各种快捷键的负担。

安装配置

推荐使用minpac等插件管理器,来安装GitHub上的vim-which-key插件。(请参阅官方安装文档

假设使用逗号键,作为前缀键(leader)

let mapleader=","

vimrc配置文件中设置timeoutlen选项,指定在按下leader键后,如果在约定时间后没有进一步的按键操作,则会在弹出窗口中显示快捷键绑定信息:

set timeoutlen=500
nnoremap <silent> <leader> :<c-u>WhichKey '<Space>'<CR>
nnoremap <silent> <localleader> :<c-u>WhichKey  ','<CR>

定义快捷键字典

首先,需要创建一个空字典:

let g:which_key_map = {}

vim-which-key插件能够自动识别已经存在的leader快捷键,你可以为其添加文字描述;使用'name'关键字,可以定义二级字典组,以便于为捷键分类显示。

let g:which_key_map.s = { 'name' : '+source' }
let g:which_key_map.s.v = 'Source-vimrc'
let g:which_key_map.t = { 'name' : '+tab' }
let g:which_key_map.t.v = 'Tab-vimrc'

对于尚不存在的快捷键,也可以直接定义命令和描述:

let g:which_key_map.f = {
      \ 'name' : '+file' ,
      \ 'n' : ['fn' , 'File Name to Clipboard'] ,
      \ 'p' : ['fp' , 'Path Name to Clipboard'] ,
      \ }

更多vim-which-key插件配置代码,请参阅实例

显示快捷键列表

在常规模式下点击前缀键(leader),将在弹出窗口(popup)中显示一级菜单,其中“+”开头的菜单项表示包含子菜单:

Popup

点击“+”后的首字母,比如",b"将显示缓冲区(buffer)子菜单下的快捷键信息:

Group

点击退格键<BACKSPACE>,将返回上一级菜单;点击<ESC>键,将关闭弹出窗口并返回常规模式。

Ver: 250716 | YYQ<上一篇 | 目录 下一篇>

星期一, 六月 16, 2025

VIM学习笔记 工具箱-PDF查看器zathura

zathura是一款高度可定制的简约文档查看器,适用于大多数Linux发行版。zathura是由PWMT社区开发的开源软件,它有以下两大特点:

  • 以键盘为中心,使用vim习惯的键位绑定,类似vim的命令行模式,沉浸式的vim操作体验;
  • 以文档为中心,极简的用户界面将最大化的屏幕空间留给文档本身,提供更专注的阅读体验。

zathura提供以下主要特性:

  • 支持PDF、Djvu、PostScript、Comic Book等多种文件格式;
  • 支持书签(bookmark);
  • 类似vim的命令行/搜索行/状态栏;
  • 使用鼠标双击进行链接跳转;
  • 使用鼠标选择文本将自动拷贝至剪贴板;
  • 支持SyncTeX技术,允许输入和输出同步,便于编辑LaTex文档

安装

以Debian为例,使用以下命令安装zathura:

$ sudo apt install zathura zathura-pdf-poppler zathura-djvu zathura-ps

针对其它平台的安装,请参阅官方文档

使用

在操作系统命令行环境下,使用以下命令即可查看文件:

$ zathura filename.pdf

zathura main window

在默认阅读模式下,使用F11键切换至全屏幕模式;使用F5键切换至演示模式;使用Tab键切换至目录模式。

zathura mode index

配置

通过编辑~/.config/zathura/zathurarc配置文件,可以自定义系统选项:

# show status bar and vertical scrollbar
set guioptions sv
# allowing to change the colors
set recolor true
set recolor-lightcolor "#000000"
set recolor-darkcolor "#E0E0E0"
set recolor-keephue true
# zoom and scroll step size
set zoom-step 20
set scroll-step 80
# copy selection to system clipboardset
set selection-clipboard clipboard
# enable incremental search
set incremental-search true
# shortcut - zoom
map <C-i> zoom in
map <C-o> zoom out

更多配置选项,请参阅官方文档

快捷键

Document Navigation文档导航
J向下移动一行 Move the document display window one line down.
K向上移动一行 Move the document display window one line up.
L向右移动一列 Move the document display window one column to the right.
H向左移动一列 Move the document display window one column to the left.
Ctrl + D向下移动半页 Move the document display window half a page down.
Ctrl + U向上移动半页 Move the document display window half a page up.
Ctrl + Y向右移动半页 Move the document display window half a page to the right.
Ctrl + T向左移动半页 Move the document display window half a page to the left.
Ctrl + B向下移动一页 Move the document display window a full page down.
Ctrl + F向上移动一页 Move the document display window a full page up.
Y向右移动一页 Move the document display window a full page to the right.
T向左移动一页 Move the document display window a full page to the left.
GG移动到第一页 Go to the first page of the current document.
Shift + G移动到最后一页 Go to the last page of the current document.
5, then Shift + G移动到第5页 Go to the fifth page of the current document.
Shift + H移动到当前页首 Go to the top of the currently selected page.
Shift + L移动到当前页尾 Go to the bottom of the currently selected page.
/向前搜索 Open forward search prompt.
?向后搜索 Open reverse search prompt.
N移动到下一匹配处 Go to the next occurrence of the current search term.
Shift + N移动到上一匹配处 Go back to the previous occurrence of the current search term.
Interface Navigation界面导航
A适合窗口高度 Fit the height of the current page to the display window.
S适合窗口宽度 Fit the width of the current page to the display window.
R顺时针旋转90度 Rotate the current document by 90 degrees clockwise.
Ctrl + N显示/隐藏 状态栏 Toggle status bar.
+放大 Zoom in
-缩小 Zoom out
=恢复缩放比例 Revert the zoom level of the document at the currently selected page.
60 + =缩放至指定比例 Set the current zoom level of the document to 60%.
Ctrl + R反转显示颜色 Invert the colors.
Shift + R重画屏幕 Refresh and redraw.
F5演示模式 presentation mode.
F11全屏幕模式 fullscreen mode.
Q退出/结束当前进程 Terminate the current Zathura session.
Linking and Indexing链接和目录
Shift + F高亮显示链接 Show all link hints.
F跳转到指定链接 Load a link hint’s page to the document viewer.
C复制链接地址 Copy a link hint’s page to the system clipboard.
Tab进入目录模式 Display the document’s content index.
J下一目录项 Scroll one item down in the document’s content index.
K上一目录项 Scroll one item up in the document’s content index.
L展开当前目录 Display the current index item’s subitems.
H折叠当前目录 Hide the current index item’s subitems.
Shift + L展开所有目录 Display all the subitems in the document’s content index.
Shift + H折叠所有目录 Hide all the subitems in the document’s content index.
Enter跳转到目录指引的页 Load the page of the currently selected index entry.
Presentation Mode演示模式
Space下一页 Go to the next document slide.
Shift + Space上一页 Go back to the previous document slide.
F5退出演示模式 Go back to the default Zathura mode.
Fullscreen Mode全屏幕模式
Shift + J下一页 Move the document display window a full page down.
Shift + K上一页 Move the document display window a full page up.
ZI放大 Zoom in
ZO缩小 Zoom out
Z0恢复缩放 Reset the current zoom level.
60 + =缩放至指定比例 Set the zoom level of the document to 60%.
F11退出全屏幕模式 Go back to the default Zathura mode.
Command Mode命令模式
:进入命令行 Open Zathura’s command prompt.
:close关闭当前文档 Exit the current document without exiting Zathura.
:open {filename}打开指定文档 Open a new document on the current Zathura window.
:blist列示书签 List all the available bookmarks for the current session.
:bmark {name}新增书签 Add the current page to the session’s bookmark index.
:bjump {name}跳转至指定书签 Jump to a bookmark.
:bdelete {name}删除书签 Delete bookmark
:exec执行外部命令 Run an external shell command on the current document.
:info显示文档信息 Print the document’s internal properties.
:print打印文档 Send the current document to the machine’s print spool.

Ver: 25.06.10 | YYQ<上一篇 | 目录 下一篇>

星期六, 六月 07, 2025

VIM学习笔记 编译文档(Compiling Latex)

LaTeX是一种用于生成高印刷质量的科技和数学类文档的排版系统,可以生成包含复杂表格和数学公式的复杂文档。

LaTeX文档是一个以.tex扩展名的文本文件,可以使用任意文本编辑器(比如Vim)编辑;在编辑完成后,可以编译为更容易阅读的文档格式(比如PDF)。

TeX Live

TeX Live是一种免费的跨平台TeX文档制作系统。它包括所有与TeX相关的主要程序、宏包和字体,支持全球多种语言。许多Unix/GNU/Linux操作系统通过自己的包管理器提供TeX Live,但强烈推荐严格按照官方文档进行手动安装。(另:参考

在安装之后,增加以下PATH路径:

export PATH=$PATH:/usr/local/texlive/2025/bin/i386-linux
export INFOPATH=$INFOPATH:/usr/local/texlive/2025/texmf-dist/doc/info
export MANPATH=$MANPATH:/usr/local/texlive/2025/texmf-dist/doc/man

同理,使用以下命令为sudo增加安全路径:

$ sudo visudo

Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2025/bin/i386-linux"

使用tlmgr更新Tex Live系统以及安装的Packages:

$ sudo tlmgr update --self

$ sudo tlmgr update --all

如果在使用过程中,仍然出现类似以下缺失包的报错信息:

! LaTeX Error: File `gb4e.sty' not found.

那么可以使用以下命令,手动安装缺失的包:

$ sudo tlmgr install gb4e

编辑Latex文档

Vim内置了Latex语法高亮度,可以很方便地编辑文档。

Latex-Syntax

编译Latex文档

在Vim命令行中执行pdfTeX命令,可以将Latex源文件编译为更容易阅读的PDF文件:

:!pdflatex sample.tex

默认情况下,将显示类似以下输出信息,并等待用户按回车键以返回到Vim:

Output written on sample.pdf (1 page, 66919 bytes).
Transcript written on sample.log.
Press ENTER or type command to continue

查看Latex输出文档

命令执行成功后,将在同一目录下,生成一个同名的PDF文件。同时还会生成一些辅助文件,如sample.log,其中包含有关编译过程的详细信息。

使用以下命令,启动PDF查看器验证生成的输出结果:

:zathura sample.pdf

Latex-View-PDF

自动化Latex编译和输出

在vimrc启动文件中自定义以下命令:

:command Latex execute "silent !pdflatex % > /dev/null && zathura %:r.pdf > /dev/null 2>&1 &" | redraw!

  • silent,将避免显示诸如 “Press ENTER to continue” 等命令执行过程中的提示信息;
  • pdlatex,将编译当前文件, % > 将标准输出重定向到/dev/null而不是显示在屏幕上;
  • zathura,用于查看生成的PDF文件,即以%:r指定的当前文件名,以.pdf指定的后缀扩展名;
  • redraw,重画屏幕,以保证正常显示;

在命令行中执行以下自定义命令,将自动化使用pdflatex来编译文档,并使用Zathura来显示生成的PDF文件:

:Latex

您也可以在vimrc启动文件中自定义以下快捷键:

:map <F2> :Latex <CR>

Ver: 25.06.07 | YYQ<上一篇 | 目录 下一篇>

星期二, 四月 29, 2025

VIM学习笔记 使用Git Bash Shell环境

Git Bash是将Git版本控制系统和Bash Shell集成至单一界面的工具,允许用户在Windows系统的命令行环境中使用Git命令,为熟悉Linux系统的开发人员提供无缝体验;同时还能利用Bash强大的脚本和命令行功能。

集成Git Bash和ConEmu

如果您已经安装了GitConEmu,那么通过以下配置将可以在虚拟终端和Vim中调用Git Bash。

在ConEmu中,点击Win+Alt+P快捷键以打开设置对话框。

Git Bash

  1. 在侧边栏中,选择Startup下的Tasks
  2. 点击+按钮,新增项目;
  3. 输入"Bash::git bash"作为项目名称;
  4. Task Parameters编辑框中,指定启动目录和图标文件:
    /single /Dir "C:\" /icon "C:\Program Files\Git\mingw64\share\git\git-for-windows.ico"
  5. Commands编辑框中,指定Git Bash命令:
    "%ProgramFiles%\Git\bin\sh.exe" --login -i
  6. 点击Save Settings按钮,保存设置。

在ConEmu中,选择新建Git Bash终端窗口:

New Console

您可以在命令行环境下管理Git库:

Command

除了Git命令,Git Bash也提供一系列Linux经典命令工具,您可以使用$ help命令列出命令列表:

Help

配置Vim使用Git Bash

通过以下命令,可以查询到Vim在Windows下,默认使用命令提示符 "Command Prompt":

:set shell?

shell=C:\WINDOWS\system32\cmd.exe

vimrc配置文件中设置以下函数:

fun! ShellGitBash()
	let $CHERE_INVOKING=1
	set shell=C:\PROGRA~1\Git\bin\sh.exe
	set shellcmdflag=--login\ -c
	set shellxquote=\"
	set shellslash
	let $PATH .= ';C:\PROGRA~1\Git\bin'
endfun

通过以下命令,则可切换至Git Bash的Shell环境:

:call ShellGitBash()

在Vim中使用:terminal命令,即可打开Git Bash终端窗口:

terminal

你也可以在Vim命令行模式下,使用:!:shell命令,来执行bash命令

Ver 25.04.28  | YYQ<上一篇 | 目录 下一篇>

VIM学习笔记 插件管理器(minpac)

Vim8引入了内置的packages软件包功能。而minpac则是构建于packages软件包功能之上的轻量化插件管理器。

安装配置

首先在操作系统中,为minpac创建安装目录:

  • Linux:
    $ mkdir -p ~/.vim/pack/minpac/opt
  • Windows:
    $ mkdir -p ~/vimfiles/pack/minpac/opt

使用以下命令,将minpac作为手动载入的插件,安装在opt目录中:

$ cd ~/vimfiles/pack/minpac/opt
$ git clone https://github.com/k-takata/minpac.git

在vimrc配置文件中,添加以下命令:

packadd minpac
call minpac#init()

安装插件

minpac支持源码托管在GitHub的插件,你可以在github.com/vim-scripts/上找到vim官网里所有插件的镜像,也可以直接引用github.com的网址。

如果希望安装新插件,可以在vimrc配置文件中,添加以下minpac#add()命令:

call minpac#add('lilydjwg/colorizer')
call minpac#add('kshenoy/vim-signature')
call minpac#add('yyq123/HTML-Editor')

默认情况下,插件将被作为自动载入插件安装至start目录。

通过在函数中指定'type': 'opt'选项,将minpac自身作为手动载入插件进行注册。

call minpac#add('k-takata/minpac', {'type':'opt'})

然后使用以下命令,重新载入vimrc配置文件,并安装插件:

:source ~/.vimrc
:call minpac#update()

安装的插件将存放在pack子目录中,目录结构如下:

Directory

卸载插件

如果希望卸载插件,请先在vimrc文件中删除相关call minpac#add('')命令;然后重新载入vimrc文件,并执行以下命令:

:call minpac#clean()

更新插件

建议在vimrc文件中,增加以下命令映射以便于后续操作:

" minpac commands:
" 安装和更新插件
command! PackUpdate call minpac#update()
" 卸载插件
command! PackClean call minpac#clean()
" 查看插件状态
command! PackStatus call minpac#status()

然后就可以使用以下命令,批量更新所有已安装的插件:

:PackUpdate

帮助信息

使用以下命令,可以查看minpac的帮助信息:

:help minpac

Ver 25.4.22 | YYQ<上一篇 | 目录 下一篇>

星期日, 四月 20, 2025

VIM学习笔记 软件包(packages)

Vim8引入了内置的packages软件包功能。Vim软件包将创建一个或多个目录,用于安装和更新插件。和普通插件管理器相比,它主要有以下优点:

  • 软件包可作为归档下载,并可解压到单独的目录。这样文件就不会和其它插件的文件混杂。也方便后续更新和删除。
  • 软件包可以使用git等版本库进行更新。
  • 软件包可包含相互依赖的多个插件。
  • 软件包可包含启动时自动载入的插件和通过:packadd手动载入的插件。

任何位于~/.vim/pack之下的目录,都将被视为软件包。一个软件包,可以将插件存储在startopt子目录中:

  • ~/.vim/pack/*/start/{name}
    存储自动载入的插件
  • ~/.vim/pack/*/opt/{name}
    存储通过:packadd {name}命令手动载入的插件

其中,*可以为任意名称。

例如使用以下命令,创建以“plugins”命名的目录结构:

mkdir -p ~/.vim/pack/plugins/start

mkdir -p ~/.vim/pack/plugins/opt

安装配置

可以clone或者使用子模块(Submodules)功能,将git库同步到本地软件包目录。这也意味着,您可以在Git库里集中管理.vimrc配置文件和.vim目录结构,并同步到不同的计算机里。

Git子模块允许你将一个Git仓库作为另一个Git仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。

pack目录创建git库:

cd ~/.vim/pack
git init
git add *
git commit -m "Initial commit"
git submodule init
git remote add origin https://github.com/username/reponame.git
git push -u origin master

目录结构如下,之后安装的插件将存放在~/.vim/pack子目录中:

packages Directory

安装插件

软件包支持源码托管在GitHub的插件,你可以在github.com/vim-scripts/上找到vim官网里所有插件的镜像,也可以直接引用github.com的网址。

如果希望安装新插件,可以执行以下命令安装指定的插件:

$ git submodule add https://github.com/chrisbra/unicode.vim plugins/start/unicode

packages-git-submodule-add

然后使用以下命令,来提交变更:

$ git commit -m "Added submodules"
$ git push origin master

使用以下命令,可以安装用于演示Vim8.2新功能的killersheep小游戏。

$ git submodule add https://github.com/vim/killersheep plugins/opt/killersheep
$ git commit -m "Added killersheep"
$ git push origin master

使用以下命令,手动载入插件:

:packadd killersheep

使用以下命令,启动游戏:

:Kill

packages-killersheep

卸载插件

如果希望卸载插件,请先执行以下命令来移除git submodule:

git submodule deinit -f -- ~/.vim/pack/plugins/opt/killersheep

然后使用以下命令,删除插件文件目录:

rm -rf ~/.vim/pack/plugins/opt/killersheep

最后使用以下命令,从远处Git库中删除插件:

git rm -rf ~/.vim/pack/plugins/opt/

更新插件

使用以下命令,可以更新指定插件'unicode':

$ cd ~/.vim/pack/plugins/start/unicode
$ git submodule update --remote --merge

使用以下命令,可以批量更新所有已安装的插件:

$ cd ~/.vim/pack
$ git submodule foreach git pull origin master

请注意,如果有任何插件更新,请使用以下命令提交更新至Git repository:

$ cd ~/.vim/pack
$ git commit -am "Updated plugins."

帮助信息

使用以下命令,可以查看packages的帮助信息:

:help packages

请注意,对于Vim内置的软件包功能:

  • 仅仅负责载入插件,而需要用户自行下载、安装和更新插件,用户友好度欠佳;
  • 调用git的clone和submodule,对于普通用户的门槛过高;
  • 需要自行生成帮助文档(Help tags),例如以下命令将生成指定目录下的帮助文档:
    :helptags ~/.vim/pack/plugins/start/reponame
    而以下命令则将生成所有帮助文档:
    :helptags ALL

如果您同时使用多台电脑,那么结合packages和git功能即可以实现插件配置的同步。当然,这过程中牵扯了诸多手工操作和git知识,平衡取舍还请各位自行斟酌。

$ git clone https://github.com/username/reponame.git ~/.vim
$ cd ~/.vim
$ git submodule update --init
$ git commit -am "Updated plugins."
$ git submodule foreach git pull origin master
$ git add .
$ git commit -m "update submodules"
命令小结
:packadd>安装插件

Ver 25.4.20 | YYQ<上一篇 | 目录 下一篇>

星期日, 十月 24, 2021

VIM学习笔记 选择模式 (Select Mode)

选择模式,可以理解为另一种可视化模式。在选择模式下,可以对选中的文本进行快速操作。比如先高亮选中文本,然后用Backspace来删除这段文本;或者先高亮选中文本,然后用输入的内容来替换这些文本。执行操作之后,Vim将自动进入插入模式(Insert Mode)

选择模式与可视化模式的主要区别在于:在可视化模式下,可以选中文本,然后执行命令操作。也就是说,要用命令来结束可视化模式。而在选择模式下,命令仅限于Backspace(用于删除操作)和可打印的字符(用于替换操作)。因为不需要输入命令,所以操作变得更为简单,但同时也存在很多限制。

进入选择模式

在常规模式下,可以使用以下命令进入选择模式。如果已经启用'showmode'选项,那么将在屏幕底部显示相应的选择模式名称:

命令描述模式名称
gh进入字符选择模式-- SELECT --
gH进入行选择模式-- SELECT LINE --
gCTRL-H进入块选择模式-- SELECT BLOCK --

可视化模式(Visual Mode)下,可以使用Ctrl + g快捷键,进入选择模式。Vim将根据当前所处的可视化模式的类型,自动进入与之一致的字符/行/块选择模式。再次点击ctrl-G键,将返回到可视化模式。

可以使用ctrl-O命令从选择模式切换到可视化模式。

选择模式下的操作

在选择模式下移动光标比在正常模式下要困难一些。因为输入任何可打印字符和回车符,Vim就会删掉已选中的文本并进插入状态。所以要选择文本就只能使用方向键、CTRL以及功能键。

如果希望使用Shift键来扩展选择,那么需要在'keymodel'选项中包含"startsel":

:set keymodel=startsel,stopsel

如果要在选择模式下用鼠标来选择文本,需要设置以下选项:

:set selectmode=mouse

退出选择模式

使用EscCtrl-[键,可以退出选择模式。

帮助信息

:help select-mode

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>

星期五, 十月 22, 2021

VIM学习笔记 脚本-字典-实例(Script-Dictionary-Example)

可以将字典(Dictionary),理解为存储了关于键-值的成对的二元数组。以下将演示在脚本中利用字典数据结构的实例。

删除重复行

文本排序(sort)章节中,使用以下Vim内置的排序命令,可以去除文件中的重复行,同时原始行的顺序也将随之改变。对于重复的多行,将仅仅保留第一行,而其它的行将被删除。

:%sort u

创建自定义函数

以下将自定义Uniq()函数,利用字典数据结构来去除重复行,同时保留原始的行顺序。

Script-UDF-Uniq
Source code: Uniq.vim

首先,新建一个空字典"have_already_seen"用于遍历指定区域内的所有行;而不重复的行将被加入到字典"unique_lines"中。

因为字典结构不会存储键为空的项目,所以在循环语句中,将为每行内容新增前导字符'>',以确保行不为空。

函数将检查行是否已经作为键存在于字典"have_already_seen"中。如果已经存在,就忽略此行;如果不存在,则被加入字典"unique_lines"中。最后,字典"unique_lines"将仅仅包含所有唯一的行,并以原始的顺序排列;而重复的行,则会被删除。

调用自定义函数

将以上代码添加到vimrc配置文件之后,使用以下命令,可以针对整个文件执行函数:

:%call Uniq()

也可以创建快捷键,针对指定的行范围(如可视化模式下选中的行)执行函数:

vmap u :call Uniq()<CR>

Script-UDF-Uniq

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>

星期四, 九月 09, 2021

VIM学习笔记 脚本-字典(Script-Dictionary)

可以将字典(Dictionary),理解为存储了关于键-值的成对的二元数组。如果你知道了键,就能快速地查找它对应的值。

创建字典

使用以花括号包含逗号分隔的键-值列表,来创建字典。每个项目包含以冒号分隔的键和值。

let flavor = {
\ '01': 'guava',
\ '02': 'mangosteen',
\ '03': 'mango',
\ '04': 'banana',
\ '05': 'coconut',
\ '06': 'passionfruit',
\ '07': 'watermelon',
\ '08': 'papya'
}

"创建空字典
let emptydict = {}

访问字典

可以使用以方括号包围的索引编号,来访问字典的值;而更直观的方式,是使用键的名称:

let Dx = flavor['09']

如果键只包含字母、数字和下划线,那么可以使用以点标记的形式进行访问。这种类似“表名.列名”的书写形式,非常类似于数据库的记录格式,显得更简洁也更易读:

let user = {}
let user.name = 'Bram'
let user.acct = 123007

如果键不存在,那么将显示以下报错信息:

E716: Key not present in Dictionary: 09

使用get()函数来访问字典的值,第一个参数指定字典,第二个参数指定需要查找的键。如果该键存在,将返回对应的值,如果指定的键不存在,则返回0。如果指定了第三个参数,那么在指定的键不存在时,则返回此参数值。

echo get(flavor, '08')          "papya
echo get(flavor, '09')          "0
echo get(flavor, '09', 'None')  "None

新增项目

使用以下命令为新的键赋值,即可为字典新增项目:

let diagnosis = {'1':'item1'}

使用extend()函数,可以为字典新增多个项目:

call extend(diagnosis, {'2':'item2', '3':'item3'})
" '1': 'item1', '2': 'item2', '3': 'item3'

使用extend()函数,也可以合并两个字典:

let new_diagnosis = {'0':'new'}
call extend(diagnosis, new_diagnosis)
" '0': 'new', '1': 'item1', '2': 'item2', '3': 'item3'

删除项目

使用remove()函数,可以删除字典中的项目:

let removed_value = remove(flavor, '09')

当需要删除多个项目时,使用filter()函数将更加高效。 与过滤列表项目类似,您可以使用v:val和v:key进行操作:

" 删除所有以0开头键值的项目
call filter(flavor, 'v:key[0] != "0"') 
" 删除所有不包含指定内容的项目
call filter(flavor, 'v:val =~ "mango"') 
" 删除所有键与值相同的项目
call filter(diagnosis, 'v:key != v:val')

字典和列表

使用以下命令,可以获取字典中的键列表,值列表,和键-值列表:

let dict = {'key1':'value1', 'key2':'value2', 'key3':'value3'}
let keylist = keys(dict)
" ['key1', 'key2', 'key3']
let valuelist =	values(dict)
" ['value1', 'value2', 'value3']
let pairlist = items(dict)
" [['key1':'value1'], ['key2':'value2'], ['key3':'value3']]

大多数关于列表的函数,同样也适用于字典:

let is_empty =	empty(dict)           " 测试字典是否为空
let entry_count = len(dict)           " 返回字典中包含项目的总数量	
let occurrences = count(dict, str)    " 返回字典中值为str的项目的数量
let greatest = max(dict)              " 返回最大值
let least = min(dict)                 " 返回最小值

使用map()函数操作数据,可以将其中的字符串格式化为首字母大写:

let names = {'u1': 'TOM', 'u2': 'jerry', 'u3': 'alEX'}
call map( names, 'toupper(v:val[0]) . tolower(v:val[1:])' )
" {'u1': 'Tom', 'u2': 'Jerry', 'u3': 'ALex'}

关于相关函数的使用说明,请参阅列表(List)章节。

帮助信息

使用以下命令,可以查看字典相关的帮助信息:

:help Dictionary
:help Dictionary-function

函数小结
len()字典的项目总数
empty()检查字典是否为空
remove()删除字典里一或多个项目
copy()建立字典的浅备份
deepcopy()建立字典的完整备份
filter()过滤指定的字典项目
map()改变每个字典项目
max()字典项目的最大值
min()字典项目的最小值
count()计算字典里某值的出现次数

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>

星期三, 一月 20, 2021

VIM学习笔记 脚本-列表(Script-List)

列表(List),是一组由逗号分隔的项目的有序序列。它与其它编程语言中的数组(Array)概念非常相似。可以使用索引号来访问列表项目。也可以在序列的任何位置上增加或者删除项目。

请注意下文中引号后的文字为命令执行的结果,以演示各个函数的功能。

创建列表

可以将一组由逗号分隔的项目放置在方括号之内,以创建一个列表。列表中的项目索引从0开始。可以使用[n]形式,来引用特定的列表项目。

let data = [1,2,3,4,5,6,"seven"]
echo data[0]                            " 1
let data[1] = 42                        " [1,42,3,4,5,6,"seven"]
let data[2] += 99                       " [1,42,102,4,5,6,"seven"]
let data[6] .= ' samurai'               " [1,42,102,4,5,6,"seven samurai"]

使用[m:n]形式,可以引用指定范围的列表项目。

let data = [-1,0,1,2,3,4,5]
let positive = data[2:6]                 " [1,2,3,4,5]

如果忽略索引的起始位置,那么将默认从列表首个项目开始;如果忽略索引的结束位置,那么将默认至列表最后一个项目。

let middle = len(data)/2                 " middle = 3
let first_half = data[: middle-1]        " data[0 : middle-1]
echo first_half                          " [-1,0,1]
let second_half	= data[middle :]         " data[middle : len(data)-1]
echo first_half                          " [2,3,4,5]

使用range()函数,可以生成一个整数值的列表。 range(max)将生成从0到max-1的列表;range(min, max)将生成包含min和max在内的连续值列表;range(min, max, step)将从min到max,按照step指定的步长来生成列表。

let seq_of_ints = range(5)               " [0,1,2,3,4]
let seq_of_ints = range(1,5)             " [1,2,3,4,5]
let seq_of_ints = range(1,10,2)          " [1,3,5,7,9]

列表嵌套

除了数值和字符串之外,列表中也可以包含嵌套的列表。

let pow = [
\   [ 1, 0, 0, 0  ],
\   [ 1, 1, 1, 1  ],
\   [ 1, 3, 9, 27 ]
\]

echo pow[2][3]     " 27

" [2],指第3个嵌套列表
" [3],指嵌套列表中的第4个项目 

引用列表

将变量赋值为列表时,实际上是将变量指向列表;如果再次将该变量赋给其它变量,那么这两个变量都将指向同一个列表。也就是说,对于实际列表值的变更,将同时影响所有指向它的变量。

let old_suffixes = ['.c', '.h', '.py']
let new_suffixes = old_suffixes
let new_suffixes[2] = '.js'
echo old_suffixes      " ['.c', '.h', '.js']
echo new_suffixes      " ['.c', '.h', '.js']

复制列表

使用copy()函数复制列表,就可以使用不同的变量,来保存不同状态下的列表值。

let old_suffixes = ['.c', '.h', '.py']
let new_suffixes = copy(old_suffixes)
let new_suffixes[2] = '.js'
echo old_suffixes      " ['.c', '.h', '.py']
echo new_suffixes      " ['.c', '.h', '.js']

请注意,copy()函数只会复制最顶层的列表,即列表的浅备份。如果顶层列表包含嵌套列表,那么嵌套的子列表,将仅仅被作为指向实际子列表的指针被复制。也就是说,对于实际子列表的更改,将同时影响所有指向它的变量。

let pedantic_pow = copy(pow)
echo pow               " [[1, 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]
echo pedantic_pow      " [[1, 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]

let pedantic_pow[0][0] = 'vague'
echo pow               " [['vague', 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]
echo pedantic_pow      " [['vague', 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]

" also changes pow[0][0] due to shared nested list

使用deepcopy()函数,则可以复制顶层列表及其包含的嵌套列表,即列表的完整备份。

let pedantic_pow = deepcopy(pow)
echo pow               " [[1, 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]
echo pedantic_pow      " [[1, 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]

let pedantic_pow[0][0] = 'vague'
echo pow               " [[1, 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]
echo pedantic_pow      " [['vague', 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]

" pow[0][0] now unaffected; no nested list is shared

拆分列表

使用split()函数,可以将字符串拆分为列表:

let words = split("one two three")          " 以空格为分隔符
echo words                                  " ['one', 'two', 'three']

let words = split("one:two three", ":")     " 以指定字符为分隔符
echo words                                  " ['one', 'two three']

合并列表

使用join()函数,可以将列表中的项目合并为字符串:

let list = ['one', 'two', 'three']
let str = join(list)                        " 使用空格连结列表项目
echo str                                    " one two three
let str = join(list, ';')                   " 使用指定字符连结列表项目
echo str                                    " one;two;three

列表长度和位置

使用以下函数,可以计算列表的长度,以及在列表中所处的位置:

let list = [1, 2, 3]
let list_length   = len(list)             " 列表的项目总数
echo list_length                          " 3
let greatest_elem = max(list)             " 列表项目的最大值
echo greatest_elem                        " 3
let least_elem    = min(list)             " 列表项目的最小值
echo least_elem                           " 1
let list_is_empty = empty(list)           " 将列表置为空
echo list_is_empty                        " 0

let week = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat','Sun']
let value_found   = index(week, 'Sun')    " 第一次出现指定值的索引位置
echo value_found                          " 0
let value_found   = index(week, 'sun')    " 如果没有找到匹配值(区分大小写)将返回-1
echo value_found                          " -1
let value_count   = count(week, 'Sun')    " 出现指定值的次数
echo value_count                          " 2

增加列表项目

call insert(list, newval)          " 在列表开头增加新项目
call insert(list, newval, idx)     " 在列表指定位置之前增加新项目
call    add(list, newval)          " 在列表末尾增加新项目

删除列表项目

call remove(list, idx)             " 删除指定位置的项目
call remove(list, from, to)        " 删除指定范围的项目

排序列表项目

let list = [3, 2, 1]
call sort(list)                   " 为列表排序
echo list                         " [1, 2, 3]
call reverse(list)                " 反转列表项目的排序
call list                         " [3, 2, 1]

过滤列表项目

使用filter({expr1}, {expr2})函数,可以对{expr1}指定的列表中的每个项目计算{expr2}表达式,以过滤掉符合指定模式的项目。

let data = [-1,0,1,2,3,4,5]
let positive = filter(copy(data), 'v:val >= 0')       " 过滤掉负数
echo positive                                         " [0,1,2,3,4,5]

let words = ['Linux', 'Unix', 'Mac']
let nnix = filter(copy(words), 'v:val !~ ".*nix"')    " 过滤包含nix的字符串
echo nnix                                             " ['Linux', 'Mac']

call filter(words, 0)                                 " 过滤掉所有项目
echo words                                            " []

修改列表项目

使用map({expr1}, {expr2})函数,可以将{expr1}指定的列表中的每个项目替换为{expr2}表达式的的计算结果。

let data = [-1,0,1,2,3,4,5]
let inc = map(copy(data), 'v:val + 1')                " 为每个成员+1
echo inc                                              " [0,1,2,3,4,5,6]

let words = ['Linux', 'Unix', 'Mac']
let cap = map(copy(words), 'toupper(v:val)')          " 将每个成员转换为大写
echo cap                                              " ['LINUX', 'UNIX', 'MAC']

连结列表

使用++=操作符,可以连结多个列表。

let activities = ['sleep', 'eat'] + ['drink']         " ['sleep', 'eat', 'drink']
let activities += ['code']                            " ['sleep', 'eat', 'drink', 'code']

请注意,操作符两侧必须均为列表。如果将列表与其它类型的数据连结,将会报错:

let activities += 'code'                              " E734: Wrong variable type for +=

常见问题

请注意,所有列表相关的函数都将修改后的列表作为返回结果,同时参数中的列表也将被修改。而通常,我们会希望返回修改后的列表,但保持原始列表不变。因此,建议使用copy()函数来复制原始列表作为参数,以避免其被修改。

let new_values = map(values, 'v:val * v:val')             " values和new_values均被修改
let new_values = map(copy(values), 'v:val * v:val')       " values保持不变

let sorted_list = reverse(sort(unsorted_list))            " unsorted_list和sorted_list均被修改
let sorted_list = reverse(sort(copy(unsorted_list)))      " unsorted_list保持不变

帮助信息

使用以下命令,可以查看列表相关的帮助信息:

:help list
:help list-functions

函数小结
len()列表的项目总数
empty()检查列表是否为空
insert()在列表某处插入项目
add()在列表后附加项目
remove()删除列表里一或多个项目
copy()建立列表的浅备份
deepcopy()建立列表的完整备份
filter()过滤指定的列表项目
map()改变每个列表项目
sort()为列表项目排序
reverse()反转列表项目的顺序
split()分割字符串成为列表
join()合并列表项目成为字符串
range()返回数值序列的列表
index()列表里某值的索引
max()列表项目的最大值
min()列表项目的最小值
count()计算列表里某值的出现次数

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>

星期四, 一月 14, 2021

VIM学习笔记 通道(channel)

假设使用以下命令,连续开启两个异步作业

:call job_start('cd ~/.vim/')
:call job_start('ls')

这些作业之间,将是相互独立的。也就是说,连续执行这两条命令,并不能进入指定目录并列示文件。第一条命令,开启一个后台作业并使用'cd'命令进入目录;第二条命令,开启另一个独立的后台作业并使用'ls'命令列示当前目录的文件。

通道概念

利用Vim内置终端功能,可以改变当前目录并列示文件:

:terminal
$ cd ~/.vim
$ ls

也就是说,terminal命令开启了一个异步作业(即shell进程),它持续等待用户的输入,并解释执行键入的shell命令。vim利用通道(channel)来与后台异步作业进行通讯。借由此机制,vim可以获取外部命令的输出和状态,并执行回调函数进行响应。而随着外部命令的结束,通道也会自动关闭。

开启通道

使用ch_open({address} [, {options}])函数,可以开启通道:

:let channel = ch_open('localhost:8765', {'callback': "MyHandler"})

在通道选项{options}中,模式"mode"规定了通讯的消息格式(即传输和读写字符串的格式)。共支持四种模式:

  • NL,利用换行符(newline)来分隔消息。使用job_start()函数启动的作业,默认使用此模式;
  • JSONjson数据交换格式。使用ch_open()函数开启的通道,默认使用json模式;
  • JS,JavaScript风格的信息格式,效率比json更高;
  • RAW,原始格式,完全由用户在回调函数中进行处理。

至于应该使用何种模式的通道,则取决于另一端程序所提供的服务。对于简单通讯可以使用 NL 模式,而复杂的服务则推荐 JSON 模式。

模式的选择,也将影响"callback"回调函数的定义。一般形式为:

func MyHandler(channel, msg)
   echo "from the handler: " . a:msg
endfunc
  • channel参数,是通道ID,即ch_open()的返回值,代表某个特定的通道;
  • msg参数,即消息内容。如果是JSON或JS模式,将会自动解码为VimL数据类型,比如嵌套的字典或列表结构等;如果是NL模式,则将其转换为去除换行符的字符串;如果是RAW模式,则保留原始信息,其中的换行符也需要用户在回调函数中自行处理。

通道交互

开启通道并与另一端的程序建立连接之后,vim可以向对方发送请求,并等待回应,以此来协同工作。

针对不同模式的通道,需要使用不同的方式来发送信息:

JSON / JSNL / RAW描述
call ch_sendexpr(channel,{expr})call ch_sendraw(channel,{string})异步发送消息
不等待响应
call ch_sendexpr(channel,{expr},
     {'callback':MyHandler})
call ch_sendraw(channel,{string},
     {'callback':'MyHandler'})
异步发送消息
指定回调函数来响应
let response =
    ch_evalexpr(channel,{expr})
let response =
    ch_evalraw(channel,{string})
同步发送消息
并等待对方响应
  • channel参数,即用于识别通道的唯一编号;
  • expr参数,指定将要发送的VimL数值或数据结构,并交由vim编码成json或js风格的字符串;
  • string参数,必须是字符串,而不能是其他复杂的VimL数据结构。

Vim实际发送的消息,为[{channel},{expr}]组成的一个二元列表;通道彼端接收消息并进行处理之后,也将由通道返回[{channel},{response}]组成的二元列表;在同一请求回应中,通道编号是相同的,据此将返回值分发到对应的回调函数。如果在发送消息时没有指定回调函数,那么将使用在ch_open()中指定的回调函数。

同步发送消息,存在阻塞的风险,但其优点是程序逻辑简单,不必使用回调函数。如果另一端的服务程序运行在本地机器,并且执行的操作耗时较短时,可以考虑使用同步消息方式。根据通道选项"timeout"键的默认设定,阻塞时间超过2000毫秒 (即2秒)时,Vim将自动终止操作。在超时或出错时,ch_evalexpr()函数将返回空字符串。

请注意,JSON和JS模式的通道也可以使用ch_sendraw()和ch_evalraw()函数,但是需要调用json_encode()和json_decode()函数来自行处理编码和解码。

通道状态

使用ch_status()函数,可以返回指定通道的状态:

:echo ch_status(channel)

状态描述
fail通道打开失败
open通道可用
buffered通道已关闭,但还有待读的数据
closed通道已关闭

使用ch_info()函数,可以返回指定通道的详细信息:

:echo ch_info(channel)

{'status': 'open', 'id': 1, 'port': 8765, 'hostname': 'localhost', 'sock_io': 'socket', 'sock_mode': 'JSON', 'sock_timeout': 2000, 'sock_status': 'open'}

函数将返回包含详细信息的字典:

描述
statusch_status()返回值
id通道号
port地址的端口号
hostname地址的机器名
sock_io"socket"
sock_mode"NL"、"RAW"、"JSON" 或 "JS"
sock_timeout以毫秒为单位的超时
sock_status"open" 或 "closed"

关闭通道

使用ch_close()函数,可以关闭指定的通道:

:call ch_close(channel)

使用套接字(socket)时,将关闭双向的套接字;使用管道 (stdin/stdout/stderr)时,将关闭所有的管道。

通道实例

在操作系统的终端中,运行Vim自带的 $VIMRUNTIME/tools/demoserver.py 演示程序,服务开始监听指定端口:

Server loop running in thread:  Thread-1
Listening on port 8765

在Vim中开启通道,连接到演示服务器:

:let channel = ch_open('localhost:8765')

此时操作系统终端中,将显示通讯开放:

=== socket opened ===

在Vim中使用以下命令,向通道彼端发送消息:

:call ch_sendexpr(channel, 'hello!')

因为没有指定回调函数,所以Vim不会显示任何回显;运行在外部终端的监听服务,将显示以下信息:

received: [1,"hello!"]
sending [1, "got it"]

在Vim中使用以下命令,向通道彼端发送消息并指定上文中定义的回调函数:

:call ch_sendexpr(channel, 'hello!', {'callback': "MyHandler"})

Vim将执行指定的回调函数,并显示以下信息:

from the handler: got it

同时运行在外部终端的监听服务,将显示以下信息:

received: [2,"hello!"]
sending [2, "got it"]

在操作系统终端中的服务程序内,输入以下命令向另一端的Vim发送消息:

["ex","echo 'hi there'"]

在Vim屏幕底部,将显示以下消息:

hi there

channel_demo

使用以下命令,可以查看关于通道的帮助信息:

:help channel

命令小结
ch_open()打开通道
ch_sendexpr()发送消息
ch_evalexpr()
ch_sendraw()
ch_evalraw()
ch_status()通道状态
ch_info()通道信息
ch_close()关闭通道

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>