3

Vim 小技巧——合并行块

 3 years ago
source link: https://lotabout.me/2015/Vim-%E5%B0%8F%E6%8A%80%E5%B7%A7%E2%80%94%E2%80%94%E5%90%88%E5%B9%B6%E8%A1%8C%E5%9D%97/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Vim 小技巧——合并行块

Table of Contents

看教程时,自己动手输入教程的例子有助于学习和理解。但有时会发现自己输入的代码跑不通,而粘贴的代码是正确的。这时我们希望能一行行地对比自己输入的代码和原始代码。用 Vim 怎么做到呢?

如果我们有两块(block)文本,如:

Line 1
Line 2
Line 3
Line 4
Line 5
Line 6

Reference Line 1
Reference Line 2
Reference Line 3
Reference Line 4
Reference Line 5
Reference Line 6

我们想将两个块逐行合并,得到如下的结果:

Line 1
Reference Line 1
Line 2
Reference Line 2
Line 3
Reference Line 3
Line 4
Reference Line 4
Line 5
Reference Line 5
Line 6
Reference Line 6

这个需求还是会时遇到的,那么如何用 Vim 来实现这样的功能呢?

顺带一提,这也是 Vim Golf 中的一道题: Interweave two blocks of text (需要梯子)

其实这个功能用脚本实现起来并不难,难的是怎么让最终的功能方便使用。其中的核心脚本是从 Merge blocks by interleaving lines 中获得。代码如下:

function! Interleave(start, end, where)
if a:start < a:where
for i in range(0, a:end - a:start)
execute a:start . 'm' . (a:where + i)
endfor
else
for i in range(a:end - a:start, 0, -1)
execute a:end . 'm' . (a:where + i)
endfor
endif
endfunction

使用时,调用 :call Interleave(8, 13, 1)。前两个参数分别指定 'Reference Line’ 块的首末行的行号。第三个参数指定目标行,即 ‘Line 1’ 的行号。

上述代码中使用的是 vim 的 Ex 命令,即::10m2 用于将第10行的文本移动到第2行后。

上述代码的功能是 OK 的,只是调用的时候需要知道 3 个行号,并且要输入很多字符。因此改进如下:

  1. 通过 visual selection (Ctrl-v)来指定 ‘Reference Line’ 块。
  2. 可以通过行号(Line number)或标签(Mark)来指定目标行号。
  3. 为选择模式添加一个相应的快捷键。

代码如下(放入 .vimrc 中):

function! Interleave(where) range
let l:where = a:where

let l:pos = getpos(l:where)
if l:where =~ "^'" && !empty(l:pos)
let l:where = l:pos[1]
endif

let l:start = a:firstline
let l:end = a:lastline

if l:start < a:where
for i in range(0, l:end - l:start)
execute l:start . 'm' . (l:where + i)
endfor
else
for i in range(l:end - l:start, 0, -1)
execute l:end . 'm' . (l:where + i)
endfor
endif
endfunction

command! -nargs=1 -range Interleave <line1>,<line2>call Interleave("<args>")
vmap <leader>j :Interleave<space>

完成后的效果参见下节。

首先是用行号指定目标行的情形:

Interleave with line number

在目标行定义新的标签 a,之后用 'a 来访问。

Interleave with mark

另外绑定快捷键 <leader>j 是因为 vim 中默认用 j 来合并行。

本文中介绍的这个功能实际出现的频率并不是特别高,大概一个月一两次。每次都想着要不单独写个插件吧。但都忘了,这次正好一次消灭了。

另外有些同学可能不喜欢看到这么多代码。那么用宏也能实现该功能,只是几乎都需要事先知道两个块之间隔的行数,或是在两者之间跳转的方法。如:

Interleave with macro

上图中按下的键为:ma:16<Enter>qqdd'apjma''q12@q。其中 <Enter> 为回车键。

这里使用了 ma 来标记目标行,用 '' 来跳转到本次跳转前的位置。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK