JSRUN 用代码说话

Potion段移动

编辑教程

Potion段移动

既然知道了段移动的工作原理,让我们重新映射这些命令来使得它们对于Potion文件起作用。

首先我们要决定Potion文件中"段"的意义。 有两对段移动命令,所以我们可以总结出两套组合,我们的用户可以选择自己喜欢的一个。

让我们使用下面两个组合来决定哪里是Potion中的段:

任何在空行之后的,第一个字符为非空字符的行,以及文件首行。
任何第一个字符为非空字符,包括一个等于号,并以冒号结尾的行。

稍微拓展我们的factorial.pn例子,这就是那些规则当作段头的地方:

# factorial.pn                              1
# Print some factorials, just for fun.

factorial = (n):                            1 2
    total = 1

    n to 1 (i):
        total *= i.

    total.

print_line = ():                            1 2
    "-=-=-=-=-=-=-=-\n" print.

print_factorial = (i):                      1 2
    i string print
    '! is: ' print
    factorial (i) string print
    "\n" print.

"Here are some factorials:\n\n" print       1

print_line ()                               1
10 times (i):
    print_factorial (i).
print_line ()

我们的第一个定义更加自由。它定义一个段为一个"顶级的文本块"。

第二个定义则严格一点。它定义一个段为一个函数定义。

自定义映射

在你的插件的repo中创建ftplugin/potion/sections.vim。 这将是我们放置段移动代码的地方。记得一旦一个缓冲区的filetype设置为potion,这里的代码就会执行。

我们将重新映射全部四个段移动命令,所以继续并创建一个骨架:

noremap <script> <buffer> <silent> [[ <nop>
noremap <script> <buffer> <silent> ]] <nop>

noremap <script> <buffer> <silent> [] <nop>
noremap <script> <buffer> <silent> ][ <nop>

注意我们使用noremap而不是nnoremap,因为我们想要这些命令也能在operator-pending模式下工作。 这样你就能使用d]]命令来删除从这到下一段之间的内容。

我们设置映射生效于buffer-local,所以它们只对Potion文件起作用,不会替换全局选项。

我们也设置了silent,因为用户不应关心我们实现段移动的细节。

使用一个函数

每个命令中实现段移动的代码会是非常相似的,所以让我们把它抽象出供映射调用的一个函数。

你将在那些创建了一些相似的映射的Vim插件中频繁看到这种策略。 比起把所有的功能堆砌于各个映射中,这样做不仅更易读,而且更易维护。

在sections.vim文件中加上下面内容:

function! s:NextSection(type, backwards)
endfunction

noremap <script> <buffer> <silent> ]]
        \ :call <SID>NextSection(1, 0)<cr>

noremap <script> <buffer> <silent> [[
        \ :call <SID>NextSection(1, 1)<cr>

noremap <script> <buffer> <silent> ][
        \ :call <SID>NextSection(2, 0)<cr>

noremap <script> <buffer> <silent> []
        \ :call <SID>NextSection(2, 1)<cr>

这里我用到了Vimscript的断行特性,因为我不想看到又长又臭的代码。 注意反斜杠是放在第二行前面进行转义的。阅读:help line-continuation以了解更多。

注意我们使用<SID>和一个脚本本地命名空间内定义的函数来避免污染全局空间。

每个映射简单地以适当参数调用NextSection实现对应的移动。 现在我们可以开始实现NextSection了。

基本移动

让我们考虑下我们的函数需要做什么。 我们想要移动光标到下一段,而移动光标,有一个简单的办法就是利用/和?命令。

编辑NextSection成这样:

function! s:NextSection(type, backwards)
    if a:backwards
        let dir = '?'
    else
        let dir = '/'
    endif

    execute 'silent normal! ' . dir . 'foo' . "\r"
endfunction

现在这个函数使用我们之前见过的execute normal!来执行/foo或?foo,取决于backwards的值。 这将是个好的开始。

继续前进,我们显然需要搜索foo以外的东西,是什么则取决于用的是段头的第一个还是第二个定义。

把NextSection改成这样:

function! s:NextSection(type, backwards)
    if a:type == 1
        let pattern = 'one'
    elseif a:type == 2
        let pattern = 'two'
    endif

    if a:backwards
        let dir = '?'
    else
        let dir = '/'
    endif

    execute 'silent normal! ' . dir . pattern . "\r"
endfunction

现在只需要补上匹配的模式了(pattern),让我们继续完成它吧。

顶级文本段

用下面一行替换掉第一个let pattern = '...':

let pattern = '\v(\n\n^\S|%^)'

如果不理解这个正则表达式是干什么的,请回忆我们正在实现的"段"的定义。

任何在空行之后的,第一个字符为非空字符的行,以及文件首行。

开头的\v强制切换为"very magic"模式,一如之前的几次。

剩下的正则表达式由两个选项组成。第一个,\n\n^\S,搜索"两个换行符,接着之后是一个非空字符"。 这正好是我们的定义中的第一种情况。

另一个是%^,在Vim中,这是一个代表文件开头的特殊正则符号。

我们现在已经到了尝试前两个映射的时机了。 保存ftplugin/potion/sections.vim并在你的Potion例子缓冲区中执行:set filetype=potion。 [[和]]命令应该可以工作,但会显得古怪。

搜索标记

你大概注意到了,在段之间移动时光标会位于真正想要移动到的地方上方的空行。 在继续阅读之前,先想想为什么会这样。

问题在于我们使用/(或?)进行搜索,而在默认情况下Vim会把光标移动到匹配开始处。 举个例子,当你执行/foo光标会位于foo中的f。

为了让Vim把光标移动到匹配结束处而不是开始处,我们可以使用搜索标记(search flag)。 试试在Potion文件中这么搜索:

/factorial/e

Vim将找到factorial并带你到那。按下几次n来在匹配处之间移动。 e标记将使得Vim把光标移动到到匹配结束处而不是开始处。在另一个方向也试试:

?factorial?e

让我们来修改我们的函数,用搜索标记来放置光标到匹配的段头的另一端。

function! s:NextSection(type, backwards)
    if a:type == 1
        let pattern = '\v(\n\n^\S|%^)'
        let flags = 'e'
    elseif a:type == 2
        let pattern = 'two'
        let flags = ''
    endif

    if a:backwards
        let dir = '?'
    else
        let dir = '/'
    endif

    execute 'silent normal! ' . dir . pattern . dir . flags . "\r"
endfunction

我们这里改动了两处。首先,我们依照段移动的类型设置flags变量的值。 现在我们仅需处理第一种情况,所以设置了标记e。

其次,我们在搜索字符串中连接dir和flags。这将依照我们搜索的方向加入?e或/e。

保存文件,切换回Potion示例文件,并执行:set ft=potion来让改动生效。 现在尝试[[和]]来看看我们的成果吧!

函数定义

是时候处理我们对"段"的第二个定义了,幸运的是这个比起第一个简单多了。 重新说一下我们需要实现的定义:

任何第一个字符为非空字符,包括一个等于号,并以冒号结尾的行。

我们可以使用一个简单的正则表达式来查找这样的行。 修改函数中第二个let pattern = '...'成这样:

let pattern = '\v^\S.*\=.*:$'

这个正则表达式比上一个没那么吓人多了。我把指出它是怎么工作的任务作为你的练习 -- 它只是我们的定义的一个直白的翻译。

保存文件,在factorial.pn处执行:set filetype=potion,然后试试新的][和[]映射。它们应该能如期工作。

在这里我们不需要搜索标记,因为默认的移动到匹配处开头正是我们想要的。

可视模式

我们的段移动命令在normal模式下一切正常,但要让它们也能在visual模式下工作,我们还需要增加一些东西。 首先,把函数改成这样:

function! s:NextSection(type, backwards, visual)
    if a:visual
        normal! gv
    endif

    if a:type == 1
        let pattern = '\v(\n\n^\S|%^)' 
        let flags = 'e'
    elseif a:type == 2
        let pattern = '\v^\S.*\=.*:$'
        let flags = ''
    endif

    if a:backwards
        let dir = '?'
    else
        let dir = '/'
    endif

    execute 'silent normal! ' . dir . pattern . dir . flags . "\r"
endfunction

两个地方改变了。首先,函数接受的参数多了一个,这样它能知道自己是否是在visual模式下调用的。 其次,如果它是在visual模式下调用的,我们执行gv来恢复可视选择区域。

为什么我们要这么做?来,让我展示给你看。 在visual模式下随意选择一些文本并执行下面命令:

:echom "hello"

Vim将显示hello,但可视模式下选择的范围也随之清空!

当用:执行一个ex模式下的命令,可视选择的范围总会被清空。 gv命令重新选择之前的可视选择范围,相当于撤销了清空。 这是个有用的命令,你会在日常工作中因此受益的。

现在我们需要更新前面的映射,传递0给新的visual参数:

noremap <script> <buffer> <silent> ]]
        \ :call <SID>NextSection(1, 0, 0)<cr>

noremap <script> <buffer> <silent> [[
        \ :call <SID>NextSection(1, 1, 0)<cr>

noremap <script> <buffer> <silent> ][
        \ :call <SID>NextSection(2, 0, 0)<cr>

noremap <script> <buffer> <silent> []
        \ :call <SID>NextSection(2, 1, 0)<cr>

这里没什么是过于复杂的。现在让我们加上visual模式映射,作为最后一块拼图。

vnoremap <script> <buffer> <silent> ]]
        \ :<c-u>call <SID>NextSection(1, 0, 1)<cr>

vnoremap <script> <buffer> <silent> [[
        \ :<c-u>call <SID>NextSection(1, 1, 1)<cr>

vnoremap <script> <buffer> <silent> ][
        \ :<c-u>call <SID>NextSection(2, 0, 1)<cr>

vnoremap <script> <buffer> <silent> []
        \ :<c-u>call <SID>NextSection(2, 1, 1)<cr>

这些映射都设置visual参数的值为1,来告诉Vim在移动之前重新选择上一次的选择范围。 这里也用到了我们在Grep Operator那几章学到的技巧。

保存文件,在Potion文件中set ft=potion,大功告成!尝试一下你的新映射吧。 像v]]和d[]这样的命令现在应该可以正常地工作了。

我们得到了什么?

这是冗长的一章,尽管我们只实现了一些看上去简单的功能,但是你学到了(并充分地练习了)下列有用的知识:

使用noremap而不是nnoremap来创建可以作为移动和动作使用的命令。
在创建相关联的映射时,使用一个单一的接受多个参数的函数来简化你的工作。
逐渐增强一个Vimscript函数的能力。
动态地组建一个`execute 'normal! ...'字符串。
结合正则表达式,使用简单的搜索来实现移动。
使用特殊的正则符号,比如%^(文件开头) 。
使用搜索标记来改变搜索的行为。
实现不会改变可视选择范围的visual模式映射
JSRUN闪电教程系统是国内最先开创的教程维护系统, 所有工程师都可以参与共同维护的闪电教程,让知识的积累变得统一完整、自成体系。 大家可以一起参与进共编,让零散的知识点帮助更多的人。
X
支付宝
9.99
无法付款,请点击这里
金额: 0
备注:
转账时请填写正确的金额和备注信息,到账由人工处理,可能需要较长时间
如有疑问请联系QQ:565830900
正在生成二维码, 此过程可能需要15秒钟