星期日, 四月 20, 2025

VIM学习笔记 软件包(packages)

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

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

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

  • ~/.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

退出选择模式

使用Esc或Ctrl-[键,可以退出选择模式。

帮助信息

: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()函数启动的作业,默认使用此模式;
  • JSON,json数据交换格式。使用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<上一篇 | 目录 | 下一篇>

星期一, 一月 04, 2021

VIM学习笔记 使用rot13加密

ROT13算法

ROT13(回转13位,英语:rotate by 13 places,有时也记为ROT-13)是一种简易的替换式密码。ROT13是一种在英文网络论坛用作隐藏八卦(spoiler)、妙句、谜题解答以及某些脏话的工具,目的是逃过版主或管理员的匆匆一瞥。ROT13被描述成“杂志字谜上下颠倒解答的Usenet点对点体”。ROT13 也是过去在古罗马开发的凯撒加密的一种变体。ROT13是它自己本身的逆反;也就是说,要还原ROT13,套用加密同样的算法即可得,故同样的操作可用再加密与解密。该算法并没有提供真正的密码学上的保全,故它不应该被套用在需要保全的用途上。它常常被当作弱加密示例的典型。

ROT13_table_with_example
Source: zh.wikipedia.org/zh-cn/ROT13

g?命令

使用 g?{motion} 命令,可以使用Rot13对{motion}跨越的文本进行编码。例如以下命令,将对当前行进行ROT13转换:

g??

使用以下命令,将对从当前行到文件末尾的文本进行ROT13转换:

:normal VGg?

使用以下命令,将对从指定行到文件末尾的文本进行ROT13转换:

:normal 10GVGg?

假设需要针对以下id属性值进行ROT13转换:

<li id="lorem">foo</li>

那么可以在g?命令中指定文本对象:

g?i"

<li id="yberz">foo</li>

以下英文笑话,精华句为ROT13所隐匿:

How can you tell an extrovert from an
introvert at NSA? Va gur ryringbef,
gur rkgebireg ybbxf ng gur BGURE thl'f fubrf.

使用以下命令,透过ROT13表格转换整片文字,该笑话的解答揭露如下:

ggVGg?

Ubj pna lbh gryy na rkgebireg sebz na
vagebireg ng AFN? In the elevators,
the extrovert looks at the OTHER guy's shoes.

再次执行该命令,将重新对文本进行解密。以此类推,可以反复加密和解密整个文件。

定义以下快捷键,也可以对整个文件进行ROT13转换:

:map <F3> ggg?G

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

:help g?

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

星期五, 十二月 18, 2020

The Clothed/Naked Maja

 

The Clothed/Naked Maja

星期四, 十二月 17, 2020

星期一, 十一月 30, 2020

VIM学习笔记 定时器(timer)

自8.0版本起,包含+timers特性的Vim提供了定时器功能。利用定时器,可以在指定延时之后触发指定操作,也可以按照固定的时间间隔来重复执行任务。

启动定时器

使用timer_start()函数,可以启动定时器并返回定时器ID:

timer_start({time}, {callback} [, {options}])

其中:

  • time,指定时间间隔,单位为毫秒(milliseconds);
  • callback,指定需要触发的回调函数;
  • options,是包含多个键值的字典选项,用于配置和控制定时器的行为

定时器选项

使用"repeat"选项,可以控制执行回调函数的次数。缺省为执行"1"次。如果希望无限循环执行,那么可以将此值设置为"-1"。

使用以下命令,设置在状态行中显示时间:

set laststatus=2
if has("win32")
    set statusline=%{strftime(\"%I:%M:%S\ \%p,\ %a\ %b\ %d,\ %Y\")}
else
    set statusline=%{strftime(\"%l:%M:%S\ \%p,\ %a\ %b\ %d,\ %Y\")}
endif

自定义以下函数,用于更新状态行:

function! UpdateStatusBar(timer)
    execute 'let &ro = &ro'
endfunction

使用以下命令启动定时器,回调函数将持续更新状态行的时间显示:

:let timer = timer_start(3000, 'UpdateStatusBar',{'repeat':-1})

定时器信息

使用不带参数的timer_info()函数,可以获取所有定时器的信息:

:echo timer_info()

timer_info_all

使用带有ID参数的timer_info()函数,可以获取指定定时器的信息:

:echo timer_info(timer)

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

项目描述
id该定时器的 ID
repeat定时器还需要重复执行的次数,无限执行则返回 -1
remaining距定时器启动还剩余的毫秒数
time计时器时间间隔(毫秒数)
paused暂停状态时返回 1,否则返回 0
callback回调函数

暂停定时器

使用timer_pause()函数,并指定第二个参数为非0数值或非空字符串,可以暂停定时器:

:call timer_pause(timer,1)

使用timer_pause()函数,并指定第二个参数为数值0或空字符串,可以对定时器取消暂停:

:call timer_pause(timer,0)

取消定时器

假设启动以下定时器,将在指定延时之后强制退出Vim:

:let timer_id = timer_start(10000, {id -> execute('quit!')})

使用timer_stop()函数,可以取消指定定时器:

:call timer_stop(timer_id)

使用timer_stopall()函数,可以取消所有定时器:

:call timer_stopall()

定时器实例

假设当前脚本文件中包含以下代码:

let s:timeouts = [5000, 10000, 30000, 1000, 1000, 3200, 500, 700]

function! s:noop(timer_id)
    let s:timeouts = insert(s:timeouts, remove(s:timeouts, len(s:timeouts) - 1))
    normal dd
   call timer_start(s:timeouts[0], function('<SID>noop'))
endfunction

call s:noop(0)

使用以下命令执行当前脚本,将从当前行开始,按照设定的时间间隔逐行删除文本:

:so %

?使用以下命令,可以查看关于定时器的帮助信息:

:help timer

函数小结
timer_start()新建定时器
timer_info()定时器信息
timer_pause()暂停或继续定时器
timer_stop()停止定时器
timer_stopall()停止所有定时器

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

星期一, 十一月 23, 2020

VIM学习笔记 作业(job)

在传统的单线程模式下,运行外部命令时,将中断用户当前的编辑操作,并等待命令完成才可以返回vim。

自8.0版本起,包含+channel和+job特性的Vim将可以支持异步操作。Vim使用作业(job)来启动进程,并利用通道(channel)和其他进程通信。

利用异步支持,可以在后台进行复杂耗时的操作(比如使用外部grep工具查找文本),不必等待外部命令结束即可返回Vim,而不中断前台的正常编辑。在外部命令结束运行时,可以通过回调函数来处理输出结果。

job_start_version

例如使用:!ls外部命令,将在屏幕底部列示目录内容,并等待用户按回车键以返回常规模式,此时用户无法进行其它操作:

shell_cmd_ls

启动作业

使用job_start()函数,可以异步执行命令,其格式为:

job_start({command} [, {options}])

其中:command,用于指定需要运行的外部系统命令;options,是包含多个键值的字典选项,用于配置和控制作业的行为。

使用以下命令,可以启动作业以执行外部命令,并立刻返回常规模式,不影响用户的后续操作:

:call job_start('ls')

作业选项

作业采用管道(pipe)将外部命令与vim联接起来,并与标准输入(stdin)、标准输出(stdout)和标准错误(stderr)输出进行交互。

在使用job_start()函数启动作业时,可以注册一个或多个回调函数来处理特定的信息。如果希望静默执行命令,而不关心输出信息,那么也可以不注册任何回调函数。

使用"callback"选项,可以同时捕获标准输出与标准错误输出;也可以分别使用"out_cb"或"err_cb"选项,来单独捕获标准输出或标准错误输出。

首先定义用于捕获输出的回调函数:

func! MyHandler (channel, msg)
    echomsg a:msg
endfunc

通过"callback"选项,指定回调函数来处理stdout和stderr的内容:

let job = job_start ('ls', {"callback": "MyHandler"})

通过"out_cb"选项,指定回调函数来处理stdout的内容:

let job = job_start ('ls', {"out_cb": "MyHandler"})

通过"err_cb"选项,指定回调函数来处理stderr的内容:

let job = job_start ('ls', {"err_cb": "ErrHandler"})

在以上回调函数中使用了:echomsg命令,因此随后可以使用:message命令来查看信息历史,以确认命令执行结果:

job_start_ls_messages

使用"in_io"、"out_io"或"err_io"选项,可以将作业管道重定向到文件或缓冲区。

使用以下命令,可以将标准输出重定向至指定缓冲区:

:let job = job_start('ls', {'out_io': 'buffer', 'out_name': 'mybuffer'})

使用以下命令,则可以打开缓冲区查看输出信息:

:sbuf mybuffer

请注意,首行包含了“Reading from channel output...”的说明文字:

job_start_ls_out_io

使用以下任一命令,均可以将标准输出重定向至指定文件:

:let job = job_start('ls -al', {'out_io': 'file', 'out_name': '/tmp/file.txt'})

:call job_start(["/bin/sh", "-c", "ls -al > /tmp/file.txt"])

?使用以下命令,可以查看作业选项的帮助信息:

:help job-options

作业状态

使用job_status()函数,可以返回指定作业的状态:

:echo job_status(job)

状态描述
run作业运行中
fail作业无法启动
dead作业启动后结束或被终止

使用job_info()函数,则可返回指定作业的详细信息:

:echo job_info(job)

job_info

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

键描述
statusjob_status()返回值
cmd启动作业的命令行参数列表
stoponexitVim结束时给作业发信号(缺省是 "term")
channeljob_getchannel()返回值
process进程ID
tty_in终端输入名,如果没有则为空
tty_out终端输出名,如果没有则为空
exitval"status" 为 "dead" 时才有效
exit_cb退出时调用的函数
termsig终止程序的信号(仅用于Unix)
仅当"status"为"dead"时才有意义
tty_type使用的虚拟控制台类型(仅用于MS-Windows)
可选值是"winpty"或"conpty"

停止作业

使用job_stop()函数,可以停止指定的作业:

:call job_stop(job)

作业实例

实例1:启动Apache HTTP Server服务

:let job = job_start('apachectl start')

使用以下命令载入首页内容,以确认服务启动成功:

:silent r ! curl localhost

实例2:在位置列表中显示命令输出

function! s:on_find(chan, msg)
      lgetexpr split(a:msg, '')
endfunction

call job_start('find . -print0', {
      \ 'out_mode': 'raw',
      \ 'callback': function('<SID>on_find')
      \ })

使用以下命令,执行包含以上命令的脚本:

:so %

使用以下命令,将在位置列表(location list)中显示外部find命令输出的文件列表:

:lopen

job_start_out_mode_raw

?使用以下命令,可以查看关于作业的帮助信息:

:help job

函数小结
job_start()启动作业
job_status()显示作业状态
job_info()显示作业的详细信息
job_stop()停止作业

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

星期五, 十一月 13, 2020

VIM学习笔记 静默执行命令(silent)

通常在使用!运行外部Shell命令时,将显示提示信息“Press ENTER or type command to continue”,需要用户点击回车键才可以返回常规模式。

shell_cmd_msg

使用silent静默执行命令

如果不希望显示提示信息,那么可以使用:silent命令:

:silent !echo 'Hello World'

如果需要清除命令本身及其输出信息,那么可以使用Ctrl-L快捷键或:redraw!命令来重画屏幕。

您可以自定义命令,来合并以上两步操作:

:command! -nargs=1 Silent execute ':silent !'.<q-args> | execute ':redraw!'

使用以下自定义命令,将首先执行外部命令,然后重画屏幕:

:Silent echo 'Hello World'

通过结合:execute命令,可以生成并执行较复杂的命令:

:silent exec "!command"

定义以下快捷键,在Linux下静默执行命令。比如使用eSpeak将文字转换为语音:

:nnoremap <leader>es :silent exec '!espeak "hello world" &'<CR>

定义以下快捷键,在Windows下使用默认程序打开当前文件。比如使用默认浏览器,打开当前编辑的HTML文档:

:nmap <Leader>x :silent ! start "1" "%:p"<CR>

后台执行命令

使用以下命令,可以利用Shell后台执行命令和重定向的能力:

:silent exec "!(ping www.vim.org >ping.out >2&1) &"

  • >ping.out,即1>ping.out,表示将命令的标准输出(stdout)重定向到名为“ping.out”的文件;因为默认值为1,所以可以省略;
  • >2&1,表示将“2”代表的标准错误(stderr)也重定向至“1”代表的标准输出(stdout);即标准输出和标准错误都输出至名为“ping.out”的文件;
  • &,表示在后台执行命令。

如果不希望外部命令输出任何信息,那么可以将标准输出指向空设备文件“/dev/null”:

:silent exec "!(ping www.vim.org >/dev/null >2&1) &"

也可以静默执行外部命令,并在新建标签页(Tab)内显示命令输出:

:silent exec "!(echo 'Hello World') > test.txt" | :tabedit test.txt

如果希望在分割窗口内显示命令输出,那么可以使用:split命令:

:silent exec "!(echo 'Hello World') > test.txt" | :sp test.txt

后台打开应用窗口

使用以下命令,将打开与当前文件同名的PDF文档。由于Zathura窗口在前台显示,所以无法在Vim窗口中继续进行编辑;关闭zathura窗口之后,也需要在Vim中点击回车键以返回常规模式:

:!zathura %:r.pdf

zathura_foreground

使用以下命令,将打开与当前文件同名的PDF文档。由于zathura窗口在前台显示,所以无法在Vim窗口中继续进行编辑;关闭zathura窗口之后,不需要在Vim中点击回车键即可返回常规模式:

:silent !zathura %:r.pdf

使用以下命令,将在后台打开与当前文件同名的PDF文档。由于zathura窗口在后台显示,所以无需关闭zathura窗口,也无需点击回车键,即可以在Vim窗口中继续进行编辑:

:silent exec '!zathura '.expand("%:r").'.pdf &'

实例:静默压缩文件

使用以下命令,可以使用Zip压缩当前文件:

:!zip test.zip %:p

屏幕将显示以下信息,并等待用户按回车键以返回常规模式:

shell_cmd_zip_msg

使用以下命令,则屏幕不会显示任何信息,并且自动返回常规模式:

:silent !zip test.zip %:p

使用以下命令,可以批量压缩所有打开的文件:

:silent bufdo !zip test.zip %:p

实例:静默载入视图

如果希望记忆光标位置和手动折叠(Fold),以便在重新打开文件时恢复到之前的编辑状态。那么可以在vimrc配置文件中,增加以下自动命令:

set viewdir=$HOME/vimfiles/views/
autocmd BufWinLeave * mkview
autocmd BufWinEnter * silent loadview

使用system()函数静默执行命令

通过调用system()函数,也可静默执行命令:

:call system('espeak "hello world" &')

函数system()和!命令都可以调用外部命令,但system()函数不会切到shell终端,而是仍停留在vim界面。所调用外部命令的输出将会被system()函数捕获,可以将其保存在VimL变量中以供后续使用。

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

:help :silent

:help system()

关于本文中使用的第三方工具,请参阅以下网址:

  • eSpeak, text to speech
  • Zathura, document viewer
  • Zip, compression and file packaging/archive utility

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

星期二, 十一月 03, 2020

VIM学习笔记 全局命令-实例(Global Command-Examples)

执行单条命令

将所有未标志为“DONE”的行,都在行尾标注“TODO”:

:g!/DONE/s/$/ TODO/

:v/DONE/s/$/ TODO/

从第5行到第10行,在每一行下插入空行:

:5,10g/^/pu _

通过调用:sort命令,可以对指定范围内的文本进行排序。例如以下命令,将对“{}”包围的CSS属性按字母排序:

:g/{/ .+1,/}/-1 sort

g_cmd_range_sort

同理,使用以下命令,可以针对“{}”包围的文本增加缩进:

:g/{/ .+1,/}/-1 >

执行多条命令

使用以下命令,可以为“Chapter”开头的标题行增加分隔线:

:g/^Chapter/t.|s/./-/g

Chapter 1
---------
Chapter 2
---------

从以上命令可以看到,通过使用“|”可以组合执行多条命令。首先使用:t命令,复制行内容;然后执行:s命令,将文本替换为横线。

使用以下命令,可以进行多次替换。对于包含“apples”或“cherries”的行,进行两次替换:首先将“apples”替换为“bananas”,然后将“cherries”替换为“oranges”:

:g /apples\|cherries/ substitute /apples/bananas/g | substitute /cherries/oranges/g

g_cmd_sub_multi

调用函数

首先在vimrc配置文件中,新增以下自定义函数:

" Delete duplicate lines
function! DelDupLine()
  if getline(".") == getline(line(".") - 1)
    norm dd
  endif
endfunction

然后使用以下命令,调用函数来删除文件中重复的行:

:g/^/ call Del()

读取和写入文件

将所有不是以2个Tab制表符开头的行,增加到文件“top2levels.otl”的末尾:

:g!/^\t\t/.w>>top2levels.otl

首先移动到文件开头,然后执行以下命令,可以搜集相对于指定字符偏移位置的行:

:g /^Chapter/ .+2w >> begin

包含“Chapter”字符的章节号、名称和描述,分处在连续的行中。模式^Chapter将查找以“Chapter”字符开头的行;.+2将定位到匹配处之后的第2行;然后使用:w命令将其追加到名为“begin”的文件中。

g_cmd_range_lines

拆分当前文件,将每一行内容,分别保存为一个文本文件(1.txt,2.txt,3,txt依次类推):

:g/^/exe ".w ".line(".").".txt"

将字符串“MARK”替换为文件“tmp.txt”的内容:

:g/^MARK$/r tmp.txt | -d

读取和写入寄存器

假设需要从以下代码中,提取所有以“TODO”开头的注释文字:

if ( m && m[2] )
    // TODO: something1
    return [ m[1].length + m[2].length ];
else {
    // TODO: something2
    return [ 1, "`" ];
}

首先,使用qaq命令来清除寄存器a的内容,做为存储提取内容的中介位置;

然后,使用以下命令将包含“TODO”的行添加到寄存器a;请注意,使用大写的寄存器编号“A”以表示向寄存器添加内容(而小写字母则表示覆盖内容);

:g/TODO/yank A

如果希望将文本同时放入指定寄存器和系统剪切板,那么可以使用以下命令:

:g/TODO/yank A | :let @+=@a

请注意,在Mac和Windows下,寄存器*和+都用于访问系统剪切板;在Linux下,寄存器+用于访问系统剪切板。

使用以下命令,可以看到内容已经被放入寄存器和剪切板:

:registers a+*

g_cmd_register_append

请注意,vim使用“^J”符号表示换行。

使用"ap命令,即可以将寄存器a中存储的TODO信息粘贴出来:

    // TODO: something1
    // TODO: something2

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

星期一, 十一月 02, 2020

VIM学习笔记 全局命令(Global Command)

:global全局命令,通常简写为:g,可以针对匹配模式的行执行编辑命令;通过与正则表达式、寄存器和标记等功能相配合,可以高效地实现复杂的操作。

使用:g命令,可以针对所有匹配模式的行执行操作。其命令格式为:

:[range]g/{pattern}/[command]

即针对在[range]范围内,所有匹配{pattern}模式的行,执行[command]命令。

命令:g!及其同义词:v,则可以针对所有不匹配模式的行执行操作。其命令格式为:

:[range]g!/{pattern}/[command]

即针对在[range]范围内,所有不匹配{pattern}条件的行,执行[command]命令。

如果没有指定[range],则针对文件中的所有行执行命令。也可以使用行地址,把全局搜索限定在指定的行或行范围内。

如果没有指定[command],则执行:print命令来显示行内容。

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

:h :g

全局查找

查找并显示文件中所有包含模式pattern的行,并移动到最后一个匹配处:

:g/pattern

查找并显示文件中所有包含模式pattern的行:

:g/pattern/p

查找并显示文件中所有精确匹配单词pattern的行:

:g/\<pattern\>/p

查找并显示第20到40行之间所有包含模式pattern的行:

:20,40g/pattern/p

查找并显示文件中所有不包行模式pattern的行,并显示这些行号:

:g!/pattern/nu

全局删除

删除包含模式patternn的行:

:g/pattern/d

删除不包含模式pattern的行:

:g!/pattern/d

删除所有空行:

:g/^$/d

删除所有空行以及仅包含空格和Tab制表符的行:

:g/^[ tab]*$/d

删除指定范围内的文本,例如以下文本中的“DESCRIPTION”部分:

:g/DESCRIPTION/,/PARAMETERS/-1d

g_cmd_range_del

全局替换

利用全局命令,可以仅针对符合查询条件的行,进行替换操作。例如使用以下命令,将包含“microsoft antitrust”的行中的“judgment”替换为“ripoff”:

:g/microsoft antitrust/s/judgment/ripoff/

可以在命令中指定查找的范围。比如以下命令,将在包含“microsoft antitrust”的前两行及后两行中进行替换:

:g/microsoft antitrust/-2,/microsoft antitrust/+2s/judgment/ripoff/c

g_cmd_range

以上命令末尾的c参数,用于提示用户对每一个替换操作进行确认。

假设希望在以“end”结尾的行中,将第1部分文字替换为“The greatest of times;”:

the best of times; the worst of times: end    

由于会尽可能多(as much text as possible)的匹配,以下命令将替换至第2个“of”处:

:g/end$/s/.*of/The greatest of/

The greatest of times: end                    

为了只替换第1部分,则需要更精确的匹配条件:

:g/end$/s/.*of times;/The greatest of times;/

The greatest of times; the worst of times: end

全局移动

将所有的行按相反的顺序排列。其中,查找模式.*将匹配所有行,m0命令将每一行移动到0行之后:

:g/.*/m0

将指定标记(Mark)之间的行按相反的顺序排列:

:'a,'bg/^/m'b

g_cmd_mark_move

将以下文本中的“DESCRIPTION”部分,上移到“SYNTAX”之前:

:g /SYNTAX/.,/DESCRIPTION/-1 move /PARAMETERS/-1

首先匹配从包含“SYNTAX”的行到包含“DESCRIPTION”的上一行;然后将这些行移动到包含“PARAMETERS”的上一行。

g_cmd_range_move

以下两条命令均可以将所有不是以数字开头的行,移动到文件末尾:

:g!/^[[:digit:]]/m$

:g/^[^[:digit:]]/m$

全局复制

使用以下命令,可以重复每一行。其中:t或:copy为复制命令:

:g/^/t.

将包含模式pattern的行,复制到文件末尾:

:g/pattern/t$

重复每一行,并以“print ''”包围:

:g/./yank|put|-1s/'/"/g|s/.*/Print '&'/

Chapter 1
Print 'Chapter 1'

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

星期四, 十月 22, 2020

VIM学习笔记 插件列表 (Plugins)

Best with Vim8 and python3 enabled.

说明:

  • 本列表完全基于作者的主观体验,既不客观也不完整;
  • 建议使用vim-plug或Vundle等插件管理器,来安装和管理插件;
  • 在“插件”列中的 ★,表示推荐的插件;
  • 在“描述”列中的【详细介绍】,链接到插件配置与使用的详细介绍。

类别

List (🔎 请点击展开/合并)

工具

junegunn/vim-plug ★ 插件管理器【详细介绍】
VundleVim/Vundle.vim 插件管理器【详细介绍】
machakann/vim-highlightedyank 高亮显示yanked内容
svermeulen/vim-yoink yank历史纪录
DavidFishburn/YankRing.vim yank历史纪录【详细介绍】
Shougo/context_filetype.vim 文件类型侦测
vim-scripts/fencview.vim ★ 多编码(Multi-Encodings)格式识别【详细介绍】
mhinz/vim-startify 自定义启动页(包含最近使用的文件、书签和保持的会话)
dstein64/vim-startuptime 启动时间分析
MTDL9/vim-verbosity 将verbose信息输出至日志文件
Shougo/vinarise.vim 十六进制(Hex)编辑器

色彩

rafi/awesome-colorschemes Awesome配色方案合辑
romainl/vim-cool 完成搜索后自动禁用高亮显示,再次查询时重新高亮显示
guns/xterm-color-table.vim xterm 256色及RGB值列表
lilydjwg/colorizer ★ 颜色代码的背景色显示相应的色彩【详细介绍】

界面

vim-airline/vim-airline 自定义状态栏
powerline/powerline 自定义状态栏
liuchengxu/vim-which-key 在pop-up窗口中显示快捷键定义
mbbill/undotree 视觉化的undo历史
sjl/gundo.vim 视觉化的undo历史
kshenoy/vim-signature 在屏幕最左侧显示标记【详细介绍】
nathanaelkane/vim-indent-guides 可视化显示缩进级别
ryanoasis/vim-devicons 根据文件类型显示图标
bagrat/vim-buffet 自定义Tabline显示缓冲区列表
t9md/vim-choosewin 类似TMUX的display-pane快速选择窗口

数据

chrisbra/csv.vim 处理按列存储的数据文件
mechatroner/rainbow_csv 以多种色彩显示CSV文件,并支持类似SQL的查询语言
vim-scripts/VisIncr 增加数值和日期等

语法

hail2u/vim-css3-syntax CSS3 syntax support to vim's built-in syntax/css.vim
cakebaker/scss-syntax.vim Syntax file for scss (Sassy CSS)
othree/html5.vim HTML5 omnicomplete and syntax
plasticboy/vim-markdown Markdown syntax highlighting
rhysd/vim-gfm-syntax GitHub Flavored Markdown syntax highlight extension
pangloss/vim-javascript Enhanced Javascript syntax
heavenshell/vim-jsdoc Generate JSDoc to your JavaScript code
elzr/vim-json Better JSON support
vim-python/python-syntax Enhanced version of the original Python syntax
Vimjas/vim-python-pep8-indent A nicer Python indentation style
vim-jp/syntax-vim-ex Improved Vim syntax highlighting
ekalinin/Dockerfile.vim Syntax and snippets for Dockerfile
tmux-plugins/vim-tmux Plugin for tmux.conf
MTDL9/vim-log-highlighting Syntax highlighting for generic log files
mboughaba/i3config.vim i3 window manager config syntax
jstrater/mpvim Macports portfile configuration files
chr4/nginx.vim Improved nginx syntax and indent
vim-syntastic/syntastic 语法检查
dense-analysis/ale 支持LSP的异步语法检查

Git

jreybert/vimagit 简化Git工作流程
lambdalisue/gina.vim 异部控制Git
airblade/vim-gitgutter 显示和控制Git变更
tpope/vim-fugitive Git plugin for Vim
mattn/vim-gist ★ 管理存储在Gist的代码片段【详细介绍】

写作

junegunn/goyo 无干扰(Distraction-free)写作
junegunn/limelight 无干扰(Hyperfocus)写作
reedes/vim-wordy 识别经常被误用的英文单词和短语
mzlogin/vim-markdown-toc 自动生成Markdown目录(TOC)标签
iamcco/markdown-preview.nvim 使用浏览器预览Markdown文件
chrisbra/unicode.vim 输入和查询unicode字符和Digraphs

快速移动

haya14busa/vim-asterisk 增强的*命令
haya14busa/vim-edgemotion 跳转到代码块的边界
easymotion/vim-easymotion 快速移动
terryma/vim-multiple-cursors 多重光标选择

自动完成

othree/csscomplete.vim 增强的CSS自动完成
prabirshrestha/asyncomplete.vim 异步自动完成
prabirshrestha/vim-lsp 异步语言服务器协议插件
mattn/vim-lsp-settings vim-lsp自动配置
ycm-core/YouCompleteMe 代码自动完成

HTML

yyq123/HTML-Editor HTML代码输入和网页预览【详细介绍】
alvan/vim-closetag 自动关闭(X)HTML标签
mattn/emmet-vim 类似emmet的HTML代码快速输入

文本对象

wellle/targets.vim 扩展文本对象
kana/vim-operator-user 自定义操作符(operators)
kana/vim-operator-replace 使用寄存器内容进行替换操作
jiangmiao/auto-pairs 自动输入和删除成对出现的括号和引号等
tpope/vim-surround 环绕字符编辑【详细介绍】
machakann/vim-sandwich ★ 环绕字符编辑【详细介绍】
kana/vim-textobj-user 自定义文本对象
terryma/vim-expand-region 渐进可视化区域选择文本
chrisbra/matchit %命令功能扩展【详细介绍】
andymass/vim-matchup %命令功能扩展
AndrewRadev/sideways.vim 匹配函数参数
osyo-manga/vim-textobj-multiblock 处理成对括号
kana/vim-textobj-function 函数相关文本对象

编程辅助

AndrewRadev/splitjoin.vim 拆分/合并行
AndrewRadev/linediff.vim 代码块比较
SirVer/ultisnips 代码片段管理
garbas/vim-snipmate 代码片段管理
honza/vim-snippets 代码片段管理
tyru/caw.vim 注释工具
ludovicchabant/vim-gutentags 管理tag文件
luochen1990/rainbow 多重色彩括号【详细介绍】
Rainbow-Parenthsis-Bundle 多重色彩括号【详细介绍】
rhysd/devdocs.vim 查询devdocs.io离线文档
rizzatti/dash.vim 查询Dash.app离线文档
KabbAmine/zeavim.vim 查询Zeal离线文档【详细介绍】

其它

junegunn/fzf.vim 集成Fzf模糊查询工具
skywind3000/asyncrun.vim 后台异步执行外部命令

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

星期五, 九月 25, 2020

VIM学习笔记 环绕字符编辑(sandwich)

sandwich 插件可以快速编辑围绕在内容两端的字符(pairs of things surrounding things),比如成对出现的括号、引号,甚至HTML/XML标签等。

sandwich支持vim的文本对象(Text Objects),比如单词、句子和段落等等。同时也支持.重复命令(Dot Command)。

安装配置

推荐您使用vim-plug或Vundle等插件管理器,来安装GitHub上的sandwich插件。

以下将利用实例来介绍sandwich插件的主要功能,请注意:

  • 在“原始文本”列中,高亮文字表示光标所在位置;
  • 在“命令”列中,为顺序执行的命令序列;
  • 在“更改效果”列中,为命令执行之后的结果。您可以参考实例文件并自行测试。

新增环绕字符

在常规模式和可视化模式下,可以使用sa命令来新增环绕字符:

模式目标
范围
新增
内容
原始文本命令更改效果
常规模式单词字符Hello Worldsaiw*Hello *World*
单词不含标点引号Hello World!saiw'Hello 'World'!
单词包含标点引号Hello World!saiW'Hello 'World!'
单词标签Hello WorldsaiwtstrongHello <strong>World</strong>
单词括号print var1, var2sa3w)print (var1, var2)
行尾括号print var1, var2sa$)print (var1, var2)
查找标签William Shakespeare said,
"Brevity is the soul of wit."
saf"temWilliam Shakespeare said,
<em>"Brevity is the soul of wit."</em>
整行字符Hello World0sa$""Hello World"
整行标签Hello World0sa$tp<p>Hello World</p>
字符可视化模式选中的文本括号print var1, var2veesa)print (var1, var2)
选中的文本标签The passion to save humanity is a
cover for the desire to rule it.
v$jsatp<p>The passion to save humanity is a
cover for the desire to rule it.</p>
行可视化模式整行新行Hello WorldVsa)(
Hello World
)
选中的文本标签
新行
The passion to save humanity is a
cover for the desire to rule it.
Vjsatp<p>
The passion to save humanity is a
cover for the desire to rule it.
</p>
块可视化模式选中的文本标签Item 1
Item 2
Item 3
<Ctrl-Q>$jjsatli<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>

plugin_sandwich_saiwt

从以上屏幕录像中可以看到:

  • 插件提供了友好的视觉反馈。将根据指定的文本对象,自动高亮显示命令将影响的操作范围。例如输入saiw命令时,将高亮显示当前单词;当在可视化模式下操作多行字符时,视觉提示将使用户更加胸有成竹。
  • 命令中的t表示标签,将在屏幕底部显示提示行,在其中输入的标签名称然后按回车键即可(并不需要输入<>);

请注意:

  1. 插入模式下,并没有专门用于输入环绕字符的快捷键。
  2. 可视化模式下的操作:
    • 首先使用快捷键进入不同类型的可视化模式。
      比如在Windows下,使用CTRL-Q键进入块视化模式;
    • 然后使用j等移动命令来选中文本;
    • 最后点击sa键,并输入环绕字符或标签。

修改环绕字符

使用sr命令可以修改环绕字符:

替换内容原始文本命令更改效果注释
引号"Hello World"srb''Hello World'使用标识符b来指代环绕字符
标签'Hello World'srbTp<p>Hello World</p>使用标识符T来输入包含尖括号的完整标签
标签<p>Hello World</p>srtth1<h1>Hello World</h1>使用标识符t来指代尖括号之内的标签名称
标签<h1>Hello World</h1>srT)(Hello World)使用标识符T来指代包含尖括号的完整标签
括号(1,2,3)srb][1,2,3]使用闭括号替换环绕字符
括号及空格(1,2,3)srb[[ 1,2,3 ]使用自定义开括号替换环绕字符并增加间隔空格

从以上sr命令可以看到,并不需要明确指明环绕字符,而可以通过标识符b来指代,既聪明又方便。

随着命令输入,会以高亮显示将被替换的环绕字符,以便在操作之前确认范围是否正确。

plugin_sandwich_srbT

删除环绕字符

使用sd命令可以删除环绕字符:

新增内容原始文本命令更改效果
引号'Hello World'sdbHello World
括号(123+4*56)/2sdb123+4*56/2
标签<div>Hello</div>sdtHello

同样在sd删除命令中,也不需要明确指明环绕字符,而可以通过标识符b来指代,大大减少了命令的复杂度。

HTML标签

假设需要针对以下文本,添加包含多个属性的HTML标签:

Hello World

可以使用以下命令,简化标签的录入:

sa$tp.class1#id1

<p class="class1" id="id1">Hello World</p>

假设需要针对以下文本段落,添加<blockquote>标签:

The passion to save humanity is a
cover for the desire to rule it.

可以使用以下命令,针对整个段落进行操作:

sa}tblockquote

<blockquote>The passion to save humanity is a
cover for the desire to rule it.</blockquote>

自定义快捷键

根据帮助文件,建议在vimrc配置文件中取消当前s键配置:

nmap s <Nop>

xmap s <Nop>

请注意,在日常操作中可以使用cl命令来代替s功能。

如果频繁使用srtt快捷键来修改标签,那么建议映射为较短的srt快捷键:

nmap srt <Plug>(operator-sandwich-replace)<Plug>(textobj-sandwich-query-a)tt

如果希望使用surround风格的快捷键,那么可以参照surround keymappings进行配置。

自定义环绕字符

通过自定义receipe,可以设置用户特有的环绕字符。例如在vimrc文件中增加以下命令,以使用 ( [ { 来指定在括号之后附带一个间隔空格:

" if you have not copied default recipes
let g:sandwich#recipes = deepcopy(g:sandwich#default_recipes)

" add spaces inside bracket
let g:sandwich#recipes += [
      \   {'buns': ['{ ', ' }'], 'nesting': 1, 'match_syntax': 1, 'kind': ['add', 'replace'], 'action': ['add'], 'input': ['{']},
      \   {'buns': ['[ ', ' ]'], 'nesting': 1, 'match_syntax': 1, 'kind': ['add', 'replace'], 'action': ['add'], 'input': ['[']},
      \   {'buns': ['( ', ' )'], 'nesting': 1, 'match_syntax': 1, 'kind': ['add', 'replace'], 'action': ['add'], 'input': ['(']},
      \   {'buns': ['{\s*', '\s*}'],   'nesting': 1, 'regex': 1, 'match_syntax': 1, 'kind': ['delete', 'replace', 'textobj'], 'action': ['delete'], 'input': ['{']},
      \   {'buns': ['\[\s*', '\s*\]'], 'nesting': 1, 'regex': 1, 'match_syntax': 1, 'kind': ['delete', 'replace', 'textobj'], 'action': ['delete'], 'input': ['[']},
      \   {'buns': ['(\s*', '\s*)'],   'nesting': 1, 'regex': 1, 'match_syntax': 1, 'kind': ['delete', 'replace', 'textobj'], 'action': ['delete'], 'input': ['(']},
      \ ]
	

使用:help textobj-sandwich命令,可以查看sandwich文本对象的帮助信息,使用:help operator-sandwich命令,可以查看sandwich操作符的帮助信息。

使用以下命令,可以查看插件的帮助文件:

:help sandwich

使用感受

一,相较surround重新发明一系列新概念,sandwich更多地重用了已经存在的元素,比如Vim的文本对象(Text Objects)和重复命令(Dot Command),以此保证操作的一致性,并大大降低学习成本。

二,sandwich提供了非常友好的视觉辅助。对于相隔较远或者多层嵌套的环绕字符,随着命令的输入将会高亮显示被影响的字符,这使用户在执行操作之前,能有机会确认命令执行范围是否准确。

我个人更喜欢sandwich的举重若轻,而不是surround的举轻若重。此诚为一家之言,还请自行斟酌。

命令小结
sa添加环绕字符
sr修改环绕字符
sd删除环绕字符

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