├── proofreader-volunteers.txt ├── .hgignore ├── license.markdown ├── .ffignore ├── README.rst ├── config.py ├── publish.sh ├── style.txt ├── chapters ├── 20.markdown ├── 30.markdown ├── 36.markdown ├── 00.markdown ├── 13.markdown ├── 47.markdown ├── 41.markdown ├── 01.markdown ├── 03.markdown ├── 07.markdown ├── 28.markdown ├── 40.markdown ├── 10.markdown ├── 44.markdown ├── 27.markdown ├── 09.markdown ├── 55.markdown ├── 04.markdown ├── 02.markdown ├── 25.markdown ├── 29.markdown ├── 06.markdown ├── 05.markdown ├── 34.markdown ├── 21.markdown ├── 50.markdown ├── 24.markdown ├── 11.markdown ├── 08.markdown ├── 56.markdown ├── 19.markdown ├── 22.markdown ├── 45.markdown ├── 42.markdown ├── 18.markdown ├── 43.markdown ├── 23.markdown ├── 48.markdown ├── 37.markdown ├── 14.markdown ├── 17.markdown ├── 26.markdown ├── 46.markdown ├── 35.markdown ├── 15.markdown ├── 12.markdown ├── 31.markdown ├── 38.markdown ├── 16.markdown ├── 39.markdown ├── 32.markdown ├── 53.markdown ├── 33.markdown ├── 52.markdown ├── 54.markdown ├── 51.markdown └── 49.markdown ├── introduction.markdown ├── nomenclature.md ├── preface.markdown └── acknowledgements.markdown /proofreader-volunteers.txt: -------------------------------------------------------------------------------- 1 | @rhdoenges 2 | @elazar 3 | @chartjes 4 | @robhudson 5 | @cemicolon 6 | @execat 7 | @alesplin 8 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | .DS_Store 4 | *.pyc 5 | *.swp 6 | *.swo 7 | *.un~ 8 | .ropeproject 9 | tags 10 | build 11 | venv 12 | -------------------------------------------------------------------------------- /license.markdown: -------------------------------------------------------------------------------- 1 | 许可协议 2 | ======== 3 | 4 | 本书版权归Steve Losh所有,非经同意,他人不得出版或作更改。 5 | 6 | 你可以在没有做任何改动且不从中牟利的前提下随意分享本书。 7 | 本书的内容只可完整地分享,不可单取其中部分章节。 8 | -------------------------------------------------------------------------------- /.ffignore: -------------------------------------------------------------------------------- 1 | syntax: literal 2 | \.DS_Store 3 | \.ropeproject 4 | tags 5 | build 6 | venv 7 | 8 | syntax: regex 9 | .*\.pyc 10 | .*\.swp 11 | .*\.swo 12 | .*\.un~ 13 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 简介 2 | ==== 3 | 4 | 仅仅是翻译的练习而已,个人英文比较烂,也不是文艺小青年,所以翻译出来的东西可能没有那么些文艺的用词 5 | 6 | 在线阅读请访问 : 《`笨方法学习Vimscript `_》 7 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | title = u'笨方法学Vimscript' 2 | author = 'Steve Losh' 3 | author_url = 'http://stevelosh.com' 4 | 5 | ga_id = 'UA-15328874-8' 6 | gauges_id = '4e8f83b7f5a1f546e200000d' 7 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | ./build.sh 5 | #rsync --delete -az build/html/ sl:/var/www/vimscript/ 6 | cd build/html 7 | scp -r -P 12306 ./ root@onefloweroneworld:/home/learnvimscriptthehardway 8 | cd ../.. 9 | -------------------------------------------------------------------------------- /style.txt: -------------------------------------------------------------------------------- 1 | Output should be enclosed in backticks. So: 2 | 3 | If you type `:echom "Hello!"` Vim will output `Hello`. 4 | 5 | Filenames should also be in backticks. 6 | 7 | Key mappings should be lowercase. 8 | 9 | nnoremap ... 10 | 11 | Keep code within 70 or so cols. 12 | 13 | Prefix code blocks with :::vim or :::text. 14 | -------------------------------------------------------------------------------- /chapters/20.markdown: -------------------------------------------------------------------------------- 1 | 变量作用域 2 | ================ 3 | 4 | 如果你之前用过像Python或者Ruby之类的动态语言,现在你可能已经熟悉了Vim脚本的变量。你会发现Vim变量的大部分内容跟你想的一样,不过有一个东西可能会不同,那就是变量的作用域。 5 | 6 | 在两个分隔的窗口中分别打开两个不同的文件,然后在其中一个窗口中执行下面的命令: 7 | 8 | :::vim 9 | :let b:hello = "world" 10 | :echo b:hello 11 | 12 | 如你所愿,Vim会显示`world`。现在切换到另外一个缓冲区再次执行`echo`命令: 13 | 14 | :::vim 15 | :echo b:hello 16 | 17 | 这一次Vim会抛出一个无法找到变量的错误, 18 | 19 | 当你在变量名中使用`b:`,这相当于告诉Vim变量`hello`是当前缓冲区的本地变量。 20 | 21 | Vim有很多不同的变量作用域,不过在使用其他类型变量作用域之前我们需要先学习更多Vim脚本编程的知识。就目前而言,你只需要记住当某个变量由一个字符和冒号开头,那么这就表示它是一个作用域变量。 22 | 23 | 练习 24 | --------- 25 | 26 | 浏览`:help internal-variables`中的作用域列表。先看看,熟悉熟悉,即使有不明白的地方也没关系。 27 | 28 | -------------------------------------------------------------------------------- /chapters/30.markdown: -------------------------------------------------------------------------------- 1 | 执行normal! 2 | =============== 3 | 4 | 既然已经学了`execute`和`normal!`,我们就可以深入探讨一个Vimscript惯用法。 5 | 执行下面的命令: 6 | 7 | :::vim 8 | :execute "normal! gg/foo\dd" 9 | 10 | 这将移动到文件的开头,查找`foo`的首次出现的地方,并删掉那一行。 11 | 12 | 之前我们尝试过用`normal!`来执行一个搜索命令却无法输入必须的回车来开始进行搜索。 13 | 结合`execute`和`normal!`将解决这个问题。 14 | 15 | `execute`允许你创建命令,因而你能够使用Vim普通的转义字符串来生成你需要的"打不出"的字符。 16 | 尝试下面的命令: 17 | 18 | :::vim 19 | :execute "normal! mqA;\`q" 20 | 21 | 这个命令做了什么?让我们掰开来讲: 22 | 23 | * `:execute "normal! ..."`:执行命令序列,一如它们是在normal模式下输入的,忽略所有映射, 24 | 并替换转义字符串。 25 | * `mq`:保存当前位置到标记"q"。 26 | * `A`:移动到当前行的末尾并在最后一个字符后进入insert模式。 27 | * `;`:我们现在位于insert模式,所以仅仅是写入了一个";"。 28 | * `\`:这是一个表示Esc键的转义字符串序列,把我们带离insert模式。 29 | * ```q``:回到标记"q"所在的位置。 30 | 31 | 看上去有点绕,不过它真的很有用:它在当前行的末尾补上一个分号并保持光标不动。 32 | 在写Javascript,C或其他以分号作为语句分隔符的语言时,一旦忘记加上分号,这样的映射将助你一臂之力。 33 | 34 | 练习 35 | --------- 36 | 37 | 重读`:help expr-quote`(你之前应该看过)来提醒你怎么用`execute`通过转义字符串传递特殊字符给`normal!`。 38 | 39 | 在翻开下一章之前,放下本书休息一下。吃一个三明治或喝一杯咖啡(译注:或者茶!), 40 | 喂一下你的宠物——如果你有的话。 41 | -------------------------------------------------------------------------------- /chapters/36.markdown: -------------------------------------------------------------------------------- 1 | 循环 2 | ======= 3 | 4 | 你可能会惊讶地发现,作为一本关于编程语言的书,在前35章里我们压根就没有提到循环! 5 | Vimscript提供了非常多的方式操作文本(比如,`normal!`), 6 | 因此循环并不像在其他大多数语言中的那么必要。 7 | 8 | 即使如此,总有一天你会需要用到它的,所以现在让我们探讨Vim支持的两种主要的循环。 9 | 10 | For循环 11 | --------- 12 | 13 | 第一种循环是`for`循环。如果你习惯了Java,C或Javascript中的`for`循环,它看上去有点古怪。 14 | 但是你会发现这种写法十分地优雅。执行下面的命令: 15 | 16 | :::vim 17 | :let c = 0 18 | 19 | :for i in [1, 2, 3, 4] 20 | : let c += i 21 | :endfor 22 | 23 | :echom c 24 | 25 | Vim显示`10`,就是把列表中的每一个元素的加起来的结果。Vimscript的`for`循环遍历整个列表 26 | (或我们待会会提到的字典)。 27 | 28 | Vimscript中不存在C风格的`for (int i = 0; i < foo; i++)`。这一开始可能难以适应, 29 | 但一旦习惯你就不会再怀念C风格的for循环了。 30 | 31 | While循环 32 | ----------- 33 | 34 | Vim也支持经典的`while`循环。执行下面命令: 35 | 36 | :::vim 37 | :let c = 1 38 | :let total = 0 39 | 40 | :while c <= 4 41 | : let total += c 42 | : let c += 1 43 | :endwhile 44 | 45 | :echom total 46 | 47 | Vim再次显示`10`。几乎每一个程序猿都熟悉这个循环,所以我们不会浪费时间讲解。 48 | 你将会很少用到它。铭记它以备不时之需。 49 | 50 | 练习 51 | --------- 52 | 53 | 阅读`:help for`. 54 | 55 | 阅读`:help while`. 56 | -------------------------------------------------------------------------------- /chapters/00.markdown: -------------------------------------------------------------------------------- 1 | 预备知识 2 | ======== 3 | 4 | 阅读本书之前,请确保您的机器已经安装了最新版的Vim,本书写作时Vim的最新版本是7.3。 5 | 新版本的Vim会向后兼容,所以本书中的内容在7.3之后的版本中应该同样有效。 6 | 7 | 本书中的内容通用,你可以任意选择console Vim或者是gVim、MacVim之类的GUI作为你的终端。 8 | 9 | 你最好习惯用Vim编辑文件。至少应该知道Vim的基本术语,如"buffer"、"window"、 10 | "normal mode"、"insert mode"、"text mode"。 11 | 12 | 如果你当前不符合上述的条件,建议你阅读命令`vimtutor`的内容、使用Vim一两个月,当你 13 | 熟练使用Vim后再阅读本书。 14 | 15 | 你需要有一些编程经验。如果没有,建议先阅读《[笨方法学Python 16 | ](http://learnpythonthehardway.org/)》,读完之后再阅读本书。 17 | > **译者注:**原文提到的《笨方法学Python》是英文的,有问题的读者可以选择阅读 18 | > [gastlygem](https://bitbucket.org/gastlygem/lpthw)翻译的中文版《[笨方法学Python 19 | > ](https://learn-python-the-hard-way-zh_cn-translation.readthedocs.org/en/1.0/intro_zh.html)》 20 | 21 | 创建Vimrc文件 22 | ------------- 23 | 24 | 如果你已经清楚`~/.vimrc`的作用并已经有了这个文件,直接跳到下一章继续吧。 25 | 26 | `~/.vimrc`文件包含了Vimscript代码,每次启动Vim时,Vim都会自动执行其中的代码。 27 | 28 | 在Linux和Mac OS X中,这个文件位于你的home文件夹,并以`.vimrc`命名。 29 | 30 | 在Windows中,这个文件位于你的home文件夹,并以`_vimrc`命名。 31 | 32 | 在*任意*系统中,在Vim中执行`:echo $MYVIMRC`命令可以快速得知这个文件的位置和名称。 33 | 文件的路径会在屏幕的底部显示。 34 | 35 | 如果你的home文件夹没有这个文件,请自行创建一个。 36 | -------------------------------------------------------------------------------- /introduction.markdown: -------------------------------------------------------------------------------- 1 | [笨方法学Vimscript][book]面向那些想学会如何自定义[Vim][]编辑器的用户。 2 | 3 | 这 *不是* 一个Vim *使用* 教程。阅读本书之前你应该适应用Vim编辑文本,并知道一些诸如 4 | "buffer", "window"以及"insert mode"这些术语的含义。 5 | 6 | 本书大致分为三个部分: 7 | 8 | * 第一部分讲述了一些Vim的基本命令,通过这些命令修改你的`~/.vimrc`可以快速、 9 | 方便地定制你的Vim环境。 10 | * 第二部分将Vimscript作为一门编程语言做深入了解,包括变量、比较还有函数。 11 | * 第三部分实例创建一个插件。 12 | 13 | 本书[版权][license]2013 归Steve Losh所有,非经同意,他人不得出版或作更改。 14 | 你可以在没有做任何改动且不从中牟利的前提下分享本书。 15 | 16 | 本书将一直免费在线阅读。 17 | 18 | 你可以用8$购买一本[电子版(PDF, epub, mobi)][leanpub]。 19 | 20 | 你可以用20$购买一本[纸质版][paper]。 21 | 22 | 你可以用40$购买一本[精装版][hard]。 23 | 24 | [leanpub]: http://leanpub.org/learnvimscriptthehardway 25 | [paper]: http://bit.ly/lvsthw-paperback 26 | [hard]: http://bit.ly/lvsthw-hardcover 27 | 28 | 可从[BitBucket][hg]和[GitHub][git]获取本书的源码。如果你发下任何错误或感觉某处 29 | 可以改善,可以随意提交问题,但是我保留本书的版权,所以,如果你感觉不爽我也能够理解。 30 | 31 | [book]: http://learnvimscriptthehardway.stevelosh.com/ 32 | [Vim]: http://www.vim.org/ 33 | [hg]: http://bitbucket.org/sjl/learnvimscriptthehardway/ 34 | [git]: http://github.com/sjl/learnvimscriptthehardway/ 35 | [license]: http://learnvimscriptthehardway.stevelosh.com/license.html 36 | -------------------------------------------------------------------------------- /chapters/13.markdown: -------------------------------------------------------------------------------- 1 | 本地缓冲区缩写 2 | ========================== 3 | 4 | 上一章讲的东西比较多,完全理解会有点难,所以这一章来点容易的。我们已经学习了如何定义本地缓冲区的映射和设置选项,现在以同样的方式来学习本地缓冲区的缩写。 5 | 6 | 打开你的`foo`和`bar`这两个文件,切换到`foo`,然后执行下面的命令: 7 | 8 | :::vim 9 | :iabbrev --- — 10 | 11 | 在文件`foo`下进入插入模式输入下面的文本: 12 | 13 | :::text 14 | Hello --- world. 15 | 16 | Vim会为你将`---`替换为“Hello“。现在切换到`bar`试试。在`bar`中替换不会发生,这是因为我们所定义的缩写被设置为只用于`foo`的本地缓冲区。 17 | 18 | 自动命令和缩写 19 | ------------------------------ 20 | 21 | 使用本地缓冲区的缩写和自动命令来创建一个简单的“snippet”系统。 22 | 23 | 执行下面的命令: 24 | 25 | :::vim 26 | :autocmd FileType python :iabbrev iff if: 27 | :autocmd FileType javascript :iabbrev iff if () 28 | 29 | 打开一个Javascript文件然后输入`iff`缩写。然后再打开一个Python文件试试。Vim会依据文件类型在当前行执行合适的缩写。 30 | 31 | 练习 32 | --------- 33 | 34 | 为你经常编辑的文件创建更多的针对不同类型的文件的“snippet”缩写。你可以为绝大多数语言创建`return`的缩写,为javascript创建`function`的缩写,以及为HTML文件创建`“`和`”`的缩写。 35 | 36 | 将你创建的snippets加入到你的`~/.vimrc`文件中。 37 | 38 | 记住:最好的学习使用这些snippets的方法是*禁用*之前你做这些事情的方式。执行`:iabbrev return NOPENOPENOPE`会*强迫*你使用缩写,这个命令在你输入return的时候不会输出任何东西。为了节省学习的时间,为你刚才创建的snippets都创建一个上面的缩写来*强迫*你使用你创建的snippets。 39 | -------------------------------------------------------------------------------- /chapters/47.markdown: -------------------------------------------------------------------------------- 1 | 更高级的语法高亮 2 | ====================================== 3 | 4 | 我们甚至可以为Vim里面的语法高亮另开一本书了。 5 | 6 | 我们将在此讲解它最后的重要内容,然后继续讲别的东西。 7 | 如果你想要学到更多,去读`:help syntax`并阅读别人写的syntax文件。 8 | 9 | 高亮字符串 10 | -------------------- 11 | 12 | Potion,一如大多数编程语言,支持诸如`"Hello,world!"`的字符串字面量。 13 | 我们应该把这些高亮成字符串。为此我们将使用`syntax region`命令。 14 | 在你的Potion syntax文件中加入下面内容: 15 | 16 | :::vim 17 | syntax region potionString start=/\v"/ skip=/\v\\./ end=/\v"/ 18 | highlight link potionString String 19 | 20 | 关闭并重新打开你的`factorial.pn`,你将看到文件结尾的字符串被高亮了! 21 | 22 | 最后一行应该很熟了。如果你不懂,重读前两章。 23 | 24 | 第一行用一个"region"添加一个语法类型分组。 25 | 区域(Regions)有一个"start"模式和一个"end"模式来指定开头和结束的位置。 26 | 这里,一个Potion字符串从一个双引号开始,到另一个双引号结束。 27 | 28 | `syntax region`的"skip"参数允许我们处理转义字符串,比如 29 | `"She said: \"Vimscript is tricky, but useful\"!"`。 30 | 31 | 如果不提供`skip`参数,Vim将在`Vimscript`之前的`"`停止匹配字符串,这不是我们想要的! 32 | 33 | 简明扼要地说,`syntax region`中的`skip`参数告诉Vim: 34 | "一旦你开始匹配这个区域,我希望你忽略`skip`匹配的内容,即使它会被当作区域结束的标志"。 35 | 36 | 花上几分钟去想透彻。如果遇到的是`"foo \\" bar"`会怎样?那会是正确的行为吗? 37 | 那*总是*正确的行为吗?放下本书,花上几分钟来认真*想一想*! 38 | 39 | 练习 40 | --------- 41 | 42 | 给单引号字符串加上语法高亮。 43 | 44 | 阅读`:help syn-region`. 45 | 46 | 阅读`:help syn-region`将比阅读本章花费更多的时间。给自己倒杯饮料,这是你应得的! 47 | -------------------------------------------------------------------------------- /chapters/41.markdown: -------------------------------------------------------------------------------- 1 | 创建一个完整的插件 2 | ====================== 3 | 4 | 在前四十来章中,我们讲解了许多基础方面的内容。 5 | 在本书的最后部分,我们将尝试从零开始为一门语言创造Vim插件。 6 | 7 | 这不是个适合懦夫的游戏。这将需要你竭尽全力。 8 | 9 | 如果你现在就想退出,那确实也不坏!你已经学到了如何在`~/.vimrc`里改善你的生活, 10 | 还有如果修复别人的插件里的bugs。 11 | 12 | 有"这就够了,我不想虚掷光阴于创造一个我将不会使用的插件"这种想法并不可耻。 13 | 现实一点。如果你不想创造一个自己想用的插件,现在就可以离开,到你想要的时候再回来吧。 14 | 15 | 如果你*真的*想要继续,确保你可以挤出一些时间。本书剩余部分将会显得困难, 16 | 而且我会假定你真的想*学点东西*,而不是仅仅慵懒地一章章翻过去。 17 | 18 | Potion 19 | ------ 20 | 21 | 我们创造的插件将为[Potion][]这门语言提供支持。 22 | 23 | Potion是由`Why the lucky stiff`在隐于江湖之前(before his disappearance)创建的一门玩具语言。 24 | 它非常的简单,所以我们就拿它一试身手。 25 | 26 | Potion跟[Io][]很像,同时又借鉴了Ruby,Lua以及其他语言。如果你未曾玩过Io,它可能看上去略古怪。 27 | 我强烈推荐你花上至少一两个小时的时间玩玩Potion。在现实生活中你不会用它, 28 | 但是它可能会改变你思考的方式并带给你新的思想。 29 | 30 | Potion的当前实现相当地粗糙。举个例子:如果你犯了语法错误,它通常会还你段错误。 31 | 不要太纠结于此。我会给你许多可用的代码示范,这样你就能更关注于Vimscript本身而非Potion。 32 | 33 | 我们的目标不是学习Potion(尽管那也挺有趣)。 34 | 我们的目标是以Potion作为一个小例子来体验写一个完整的Vim插件的方方面面。 35 | 36 | 练习 37 | --------- 38 | 39 | 下载并安装[Potion][]。这个就要你自己动手了。它应该会比较简单的。 40 | 41 | 确保你可以在Potion解释器和以`.pn`文件的形式运行小册子里的第一个示例代码。 42 | 如果解释器貌似不能工作,看[这个issue](https://github.com/fogus/potion/issues/12)来查找可能的原因。 43 | 44 | [Potion]: http://fogus.github.com/potion/index.html 45 | [Io]: http://iolanguage.com/ 46 | -------------------------------------------------------------------------------- /chapters/01.markdown: -------------------------------------------------------------------------------- 1 | 打印信息 2 | ======== 3 | 4 | Vimscript中,我们最先关注的是`echo`和`echom`命令。 5 | 6 | 你可以在Vim中执行`:help echo`和`:help echom`命令以查看其帮助文档。读完本书之后, 7 | 再次遇到新的命令时,你应该先执行`:help`命令查看其帮助文档。 8 | 9 | 执行如下命令,体验`echo`命令: 10 | 11 | :::vim 12 | :echo "Hello, world!" 13 | 14 | 你应该会在屏幕的底部看到`Hello, world!`被打印出来。 15 | 16 | 还是打印消息 17 | ------------ 18 | 19 | 现在执行如下命令,体验`echom`命令: 20 | 21 | :::vim 22 | :echom "Hello again, world!" 23 | 24 | 你应该会在屏幕的底部看到`Hello again, world!`被打印出来。 25 | 26 | 执行如下命令,查看上述两个打印命令的区别: 27 | 28 | :::vim 29 | :messages 30 | 31 | 你应该会看到一些消息。`Hello, world!`应该*不在*其中,但是`Hello again, world!` 32 | *在*。 33 | 34 | 当你写更为复杂的Vimscript时,你可能会想"打印一些信息"以方便调试程序。`:echo`命令 35 | 会打印输出,但是一旦你的脚本运行完毕,那些输出信息就会消失。使用`:echom`打印的信息 36 | 会保存下来,你可以执行`:messages`命令再次查看那些信息。 37 | 38 | 注释 39 | ---- 40 | 41 | 继续之前,咱们先看看如何添加注释。当你写Vimscript脚本时(在你的`~/.vimrc`文件中或 42 | 其它任意文件),你可以通过`"`字符添加注释,例如: 43 | 44 | :::vim 45 | " Make space more useful 46 | nnoremap za 47 | 48 | 这个注释方法并不*总是*有效(这就是Vimscript令人无语的一点),但是更多的情况这个方法是 49 | 可以正常工作的。以后我们会谈到什么情况、为什么这个方法会无效。 50 | 51 | 练习 52 | ---- 53 | 54 | 阅读`:help echo`帮助文档。 55 | 56 | 阅读`:help echom`帮助文档。 57 | 58 | 阅读`:help messages`帮助文档。 59 | 60 | 添加一行代码到你的`~/.vimrc`文件中,使得每个打开Vim时都会显示一个可爱的ASCII 61 | 字符猫(`>^.^<`)。 62 | -------------------------------------------------------------------------------- /nomenclature.md: -------------------------------------------------------------------------------- 1 | 术语表(欢迎补充) 2 | ===== 3 | 4 | ### A 5 | 6 | append 附加 7 | 8 | autocmd 自动命令 9 | 10 | abbreviation abbreviation 11 | 12 | ### B 13 | 14 | buffer 缓冲区 15 | 16 | ### F 17 | 18 | flag 标记 19 | 20 | fold 折叠 21 | 22 | foldlevel foldlevel 23 | 24 | ### G 25 | 26 | group 组 27 | 28 | ### H 29 | 30 | help 帮助 31 | 32 | highlight 高亮 33 | 34 | ### I 35 | 36 | idiom 惯用法 37 | 38 | indent[ation] level 缩进等级 39 | 40 | ### L 41 | 42 | literal 字面量 43 | 44 | literal string 字面量字符串 45 | 46 | ### M 47 | 48 | map 映射 49 | 50 | modal 模式 51 | 52 | motion 动作 53 | 54 | movement 移动 55 | 56 | ### O 57 | 58 | omnicomplete omnicomplete 59 | 60 | operator-pending operator-pending(译注:该映射用于把一个函数映射成运算符) 61 | 62 | option 选项 63 | 64 | ### R 65 | 66 | regex 正则表达式 67 | 68 | repo repo 69 | 70 | repository 代码库 71 | 72 | ### S 73 | 74 | script-local 脚本本地命名空间 75 | 76 | section 段 77 | 78 | section heading macro 段头符 79 | split 分割 80 | 81 | statusline 状态条 82 | 83 | string literals 字符串字面量 84 | 85 | syntax group 语法类型组 86 | 87 | ### T 88 | 89 | tab tab 90 | 91 | top level 顶级 92 | 93 | ### V 94 | 95 | visual 可视(不过有时涉及到visual mode的时候不译) 96 | 97 | ### W 98 | 99 | window 窗口 100 | -------------------------------------------------------------------------------- /chapters/03.markdown: -------------------------------------------------------------------------------- 1 | 基本映射 2 | ======== 3 | 4 | 如果说Vimscript有一个特性使得你能够按照你的意愿定制Vim,那就非键盘映射莫属。 5 | 你可以通过键盘映射告诉Vim: 6 | 7 | > 当我按下这个键时,我需要你放弃默认操作,按我的想法做。 8 | 9 | 我们先从normal模式的键盘映射开始。我们将在下一章节讨论insert模式和其他模式下的 10 | 键盘映射。 11 | 12 | 随意在文本中敲写几行文字,然后运行命令: 13 | 14 | :::vim 15 | :map - x 16 | 17 | 将光标置于文本中的某处,按下`-`。注意Vim删除了当前光标下的字符,就好像你按了`x`一样。 18 | 19 | 我们本来就有个按键用于 "删除当前光标下的字符" ,所以我们将`-`重新映射到稍微有用的 20 | 功能。执行命令: 21 | 22 | :::vim 23 | :map - dd 24 | 25 | 现在移动光标到任意一行,再按下`-`,这次Vim删除了整行的文本,因为`dd`的功能就是删除整行。 26 | 27 | 特殊字符 28 | -------- 29 | 30 | 你可以使用``告诉Vim一个特殊的按键。尝试如下命令: 31 | 32 | :::vim 33 | :map viw 34 | 35 | 移动光标到一个单词上,按下空格键。Vim将高亮选中整个单词。 36 | 37 | 你也可以映射修饰键入Ctrl和Alt。执行: 38 | 39 | :::vim 40 | :map dd 41 | 42 | 现在在键盘上按下`Ctrl+d`将执行`dd`命令。 43 | 44 | 注释 45 | ---- 46 | 47 | 还记得我们在第一章讨论的注释么?键盘映射就无法使用注释。尝试如下命令: 48 | 49 | :::vim 50 | :map viw " Select word 51 | 52 | 现在你再按下空格键,一些恐怖的事情就会发生。想一想为什么会这样呢? 53 | 54 | 当你按下空格键时,Vim认为你是想执行命令`viw"Selectword`。 55 | 很明显,这不是你的本意。 56 | 57 | 如果你仔细查看了这个映射的结果,可能你会发现一些奇怪的事。利用几分钟时间, 58 | 弄明白使用这个映射时到底发生了什么,以及 *为什么* 会是那样的结果。 59 | 60 | 暂时搞不明白也不要担心,我们很快就会再次谈论这个问题。 61 | 62 | 练习 63 | ---- 64 | 65 | 映射按键`-`为 "删除当前行,然后将其粘贴到下一行" 。然后你就可以一次按键就将一行 66 | 文本移动到下一行。 67 | 68 | 将那个映射命令添加到你的`~/.vimrc`文件中,以后每次启动Vim你都可以使用那个映射了。 69 | 70 | 试试如何映射按键`_`,使其将当前行上移一行。 71 | 72 | 将这个映射也加到你的的`~/.vimrc`文件中。 73 | 74 | 想想如果你想取消一个映射或重置一个按键为默认功能时该怎么操作。 75 | -------------------------------------------------------------------------------- /chapters/07.markdown: -------------------------------------------------------------------------------- 1 | 编辑你的Vimrc文件 2 | ================= 3 | 4 | 在继续学习Vimscript之前,我们先找个添加新映射到`~/.vimrc`文件中的更方便的方法。 5 | 6 | 有时你正在疯狂的编码,突然发现加个映射会加速你的进度。你要立即将其加到`~/.vimrc` 7 | 文件中以防止忘记,但是你 *不想* 退出当前的文件,因为灵感稍纵即逝。 8 | 9 | 本章的主题是你想使编辑文件更为方便变得更为方便。 10 | 11 | 有点绕,但我没有拼错。再读一次。 12 | 13 | 本章的主题是你想使(((编辑文件)更为方便)变得更为方便)。 14 | 15 | 编辑映射 16 | -------- 17 | 18 | 我们在一个分屏中打开`~/.vimrc`文件以快速编辑添加映射,然后退出继续编码。运行命令: 19 | 20 | :::vim 21 | :nnoremap ev :vsplit $MYVIMRC 22 | 23 | 我称这个命令为“ **编辑** 我的 **vimrc** 文件”。 24 | 25 | `$MYVIMRC`是指定你的`~/.vimrc`文件的特殊Vim变量。现在不要担心,相信我不会有问题。 26 | 27 | `:vsplit`打开一个新的纵向分屏。如果你喜好横向的分屏,你可以用`:split`替换它。 28 | 29 | 花一分钟彻底理解理解那个映射命令。命令的目的是:在一个新的分屏中打开我的`~/.vimrc`。 30 | 它是如何工作的?映射中的每一个字符都是必不可少的? 31 | 32 | 通过哪个映射,你只要三个键就可以打开你的`~/.vimrc`文件。只要你多用几次,你就能 33 | 半秒内敲出这个命令。 34 | 35 | 当你编码过程中突然想到一个可以提高效率的新映射要加到`~/.vimrc`文件中, 36 | 现在对你来说简直就是小菜一碟。 37 | 38 | 重读映射配置 39 | ------------ 40 | 41 | `~/.vimrc`文件添加一个映射并不是立即生效的。`~/.vimrc`文件只在你启动Vim的时候才会读取。 42 | 也就是说在当前的session中你还需要痛苦的再次拼写那个完整的命令。 43 | 44 | 我们加个映射来解决这个问题: 45 | 46 | :::vim 47 | :nnoremap sv :source $MYVIMRC 48 | 49 | 我称这个命令为“ **重读** 我的 **vimrc** 文件”。 50 | 51 | `source`命令告诉Vim读取指定的文件,并将其当做Vimscript执行。 52 | 53 | 现在在编码时你可以方便的添加新映射了。 54 | 55 | * `ev`打开配置文件。 56 | * 添加映射。 57 | * 使用`:wq`或`ZZ`保存文件并关闭分屏,回到之前的文件。 58 | * 使用`sv`重读配置使修改生效。 59 | 60 | 定义一个映射需要8次按键。减少了中断思维的可能性。 61 | 62 | 练习 63 | ---- 64 | 65 | 在你的`~/.vimrc`文件中添加映射,温习“编辑`~/.vimrc`”和“重读`~/.vimrc`”过程。 66 | 67 | 多练几遍,随意加些没意义的映射。 68 | 69 | 阅读`:help myvimrc`。 70 | -------------------------------------------------------------------------------- /preface.markdown: -------------------------------------------------------------------------------- 1 | 前言 2 | ==== 3 | 4 | 程序猿们更喜欢实现自己的idea。 5 | 6 | idea形成算法,而算法又发展为算法,并使 *idea成真* 7 | 8 | 作为一个程序猿,我们使用文本编辑器记录我们的idea,并为写程序实现它。全职的程序猿 9 | 一生中的千分之十的时间都是在和他的文本编辑器打交道,这期间他们的所做的事情包括: 10 | 11 | * 将他们的灵感记录到计算机上 12 | * 重新考虑并修改灵感中的错误 13 | * 用代码实现他们的灵感 14 | * 写文档记录某功能是如何及为什么那么实现 15 | * 与其他的程序猿交流这个经验 16 | 17 | Vim是一个功能相当强大的编辑器,当然,前提是你需要根据你的工作、喜好以及习惯定制 18 | 它。本书将向你介绍Vimscript,一门用于定制Vim的脚本语言。读完本书你将能够定制 19 | Vim使其更加适应你的文本编辑需求,以后再使用Vim将有更好的体验。 20 | 21 | 同时我也会提到一些与Vimscript关系不大的点,但那些内容通常都能加强你对Vimscript的认知。 22 | 如果你一天仅很少的时间使用Vim,学习Vimscript对你没有多大意义,所以请慎重考虑并平衡你的时间。 23 | 24 | 25 | 本书的写作风格与其他多数的编程书籍略有不同。本书将引领你敲写命令并查看其背后的奥秘 26 | ,而不是仅仅简单的告诉你Vimscript是如何工作的。 27 | 28 | 有时本书会带你进入死胡同,然后才会给你解释解决问题的"正确方法"。其他的书籍通常不这么做, 29 | 或者仅仅在解决问题 *之后* 提到其中的技巧。然而这并不是现实世界中事情的进展顺序。 30 | 你时常会快速写一些Vimscript的代码段,运行却遇到不明缘由的故障。细致研读此书,不要 31 | 局限于浏览,读完之后再次遇到上述问题你将能够顺利解决了。熟能生巧嘛! 32 | 33 | 本书的每一章节都只讲述一个主题。每一章节都内容简短而信息丰富,所以不要跳过任何章节。 34 | 如果你真想从本书中学到东西,你要动手敲写所有的命令。可能你已经是一个经验丰富的程序猿, 35 | 能够快速阅读并理解代码的含义。但是不要掉以轻心,学习Vim/Vimscript有个与其他普通程序 36 | 语言更加不同的体验。 37 | 38 | 你需要 **敲写 *所有的* 命令** 39 | 40 | 你需要 **完成 *所有的* 练习** 41 | 42 | 两个理由解释上述内容的重要性!第一,Vimscript语言是一门古老的语言,其设计难免存在一些 43 | 不妥之处。一个简单的配置项就可影响整个脚本的功能。敲写 *每个* 章节遇到的 *每个* 命令 44 | ,完成 *每个* 练习,你就会发现很容易地发现并修复遇到的问题。 45 | 46 | 第二,Vimscript其实 *就是* Vim命令。在Vim中,保存一个文件使用命令`:write`(或者缩写 47 | `:w`)并回车确认。在Vimscript中,使用`write`实现文件保存功能。Vimscript中的许多 48 | 命令都可用于日常文件编辑工作,必须勤加练习以记住那些命令才会有用, 49 | 仅仅是看过是无法运用自如的。 50 | 51 | 我希望本书能够对你有所帮助。本书 *不是* 一本对Vimscript的综合指南。本书试图让你 52 | 掌握Vimscript,能够利用它定制你的Vim环境,为其他用户编写一些简单的插件, 53 | 能够阅读他人的代码(利用`:help`命令),能够分辨出一些常见的语法陷阱。 54 | 55 | 祝你好运! 56 | -------------------------------------------------------------------------------- /chapters/28.markdown: -------------------------------------------------------------------------------- 1 | Execute命令 2 | ======= 3 | 4 | `execute`命令用来把一个字符串当作Vimscript命令执行。在前面的章节我们曾经跟它打过交道, 5 | 不过随着对Vimscript中的字符串有更深入的了解,现在我们将再次认识它。 6 | 7 | `execute`基本用法 8 | --------------- 9 | 10 | 执行下面的命令: 11 | 12 | :::vim 13 | :execute "echom 'Hello, world!'" 14 | 15 | Vim把`echom 'Hello, world!'`当作一个命令,而且尽职地在把它输出的同时将消息记录下来。 16 | Execute是一个非常强大的工具,因为它允许你用任意字符串来创造命令。 17 | 18 | 让我们试试一个更实用的例子。先在Vim里打开一个文件作为准备工作,接着使用`:edit foo.txt`在同一个窗口创建新的缓冲区。 19 | 现在执行下面的命令: 20 | 21 | :::vim 22 | :execute "rightbelow vsplit " . bufname("#") 23 | 24 | Vim将在第二个文件的右边打开第一个文件的竖直分割窗口(vertical split)。为什么会这样? 25 | 26 | 首先,Vim将`"rightbelow vsplit"`和`bufname('#')`调用的结果连接在一起,创建一个字符串作为命令。 27 | 28 | 我们过一段时间才会讲到相应的函数,现在姑且认为它返回前一个缓冲区的路径名。 29 | 你可以用`echom`来确认这一点。 30 | 31 | 待`bufname`执行完毕,Vim将结果连接成`"rightbelow vsplit bar.txt"`。 32 | `execute`命令将此作为Vimscript命令执行,在新的分割里打开该文件。 33 | 34 | Execute危险吗? 35 | --------------------- 36 | 37 | 在大多数编程语言中使用诸如"eval"来构造可执行的字符串是会受到谴责的(如果不会是更严重的后果)。 38 | 因为两个原因,Vimscript中的`execute`命令能免于操这份心。 39 | 40 | 首先,大多数Vimscript代码仅仅接受唯一的来源——用户的输入。 41 | 假设有用户想输入一个古怪的字符串来执行邪恶的命令,无所谓,反正这是他们自己的计算机! 42 | 然而在其他语言里,程序通常得接受来自不可信的用户的输入。Vim是一个特殊的环境, 43 | 在此无需担心一般的安全性问题。 44 | 45 | 第二个原因是因为Vimscript有时候处理问题的方式过于晦涩难懂且稀奇古怪。 46 | 这时`execute`会是完成任务的最简单,最直白的方法。 47 | 在大多数其他语言中,使用"eval"不会省下你多少击键的生命,但在Vimscript里这样做可以化繁为简。 48 | 49 | 练习 50 | --------- 51 | 52 | 浏览`:help execute`来明了哪些命令你可以用`execute`实现而哪些不可以。 53 | 但当涉猎,因为我们很快将重新审视这个问题。 54 | 55 | 阅读`:help leftabove`,`:help rightbelow`,`:help :split`和`:help :vsplit`(注意最后两个条目中额外的分号)。 56 | 57 | 在你的`~/.vimrc`中加入能在选定的分割(竖直或水平,上/下/左/右方位)中打开前一个缓冲区的映射。 58 | -------------------------------------------------------------------------------- /chapters/40.markdown: -------------------------------------------------------------------------------- 1 | 路径 2 | ===== 3 | 4 | Vim是一个文本编辑器,而文本编辑器(经常)处理文本文件。文本文件储存在文件系统中, 5 | 而我们使用路径来描述文件。Vimscript有一些内置的方法会在你需要处理路径时帮上大忙。 6 | 7 | 绝对路径 8 | -------------- 9 | 10 | 有时外部脚本也需要获取特定文件的绝对路径名。执行下面的命令: 11 | 12 | :::vim 13 | :echom expand('%') 14 | :echom expand('%:p') 15 | :echom fnamemodify('foo.txt', ':p') 16 | 17 | 第一个命令显示我们正在编辑的文件的相对路径。`%`表示"当前文件"。 18 | Vim也支持其他一些字符串作为`expand()`的参数。 19 | 20 | 第二个命令显示当前文件的完整的绝对路径名。字符串中的`:p`告诉Vim你需要绝对路径。 21 | 这里也有许多别的修饰符可以用到。 22 | 23 | 第三个命令显示了当前文件夹下的文件`foo.txt`的绝对路径,无论文件是否存在。(译注:试一下看看文件不存在的情况?) 24 | `fnamemodify()`是一个比`expand()`灵活多了的Vim函数, 25 | 你可以指定任意文件名作为`fnamemodify()`的参数,而不仅仅是`expand()`所需要的那种特殊字符串。 26 | 27 | 列出文件 28 | ------------- 29 | 30 | 你可能想要得到一个特定文件夹下的文件列表。执行下面的命令: 31 | 32 | :::vim 33 | :echo globpath('.', '*') 34 | 35 | Vim将输出当前目录下所有的文件和文件夹。`globpath()`函数返回一个字符串, 36 | 其中每一项都用换行符隔开。为了得到一个列表,你需要自己去`split()`。执行这个命令: 37 | 38 | :::vim 39 | :echo split(globpath('.', '*'), '\n') 40 | 41 | 这次Vim显示一个包括各个文件路径的Vimscript列表。 42 | 如果你的文件名里包括了换行符,那就只能由你自己想办法了。 43 | 44 | `globpath()`的通配符(wildcards)的工作方式就像你所想的一样。执行下面的命令: 45 | 46 | :::vim 47 | :echo split(globpath('.', '*.txt'), '\n') 48 | 49 | Vim显示一个当前文件夹下的所有`.txt`文件组成的列表。 50 | 51 | 你可以用`**`递归地列出文件。执行这个命令: 52 | 53 | :::vim 54 | :echo split(globpath('.', '**'), '\n') 55 | 56 | Vim将列出当前文件夹下的所有文件及文件夹。 57 | 58 | `globpath()`*非常地*强大。在你完成本章练习后,你将学到更多内容。 59 | 60 | 练习 61 | --------- 62 | 63 | 阅读`:help expand()`. 64 | 65 | 阅读`:help fnamemodify()`. 66 | 67 | 阅读`:help filename-modifiers`. 68 | 69 | 阅读`:help simplify()`. 70 | 71 | 阅读`:help resolve()`. 72 | 73 | 阅读`:help globpath()`. 74 | 75 | 阅读`:help wildcards`. 76 | -------------------------------------------------------------------------------- /chapters/10.markdown: -------------------------------------------------------------------------------- 1 | 锻炼你的手指 2 | ===================== 3 | 4 | 这一章我们会讲到怎么更有效地学习Vim,不过在此之前需要先做一些小小的准备。 5 | 6 | 让我们先创建一个mapping,这个mapping会为你的左手减轻很多负担。执行下面的命令: 7 | 8 | :::vim 9 | :inoremap jk 10 | 11 | ok,现在进入插入模式然后敲击`jk`。Vim会返回到常用模式,就像你敲击了escape按键一样。 12 | 13 | 在Vim中有很多默认的方式可以退出插入模式: 14 | 15 | * `` 16 | * `` 17 | * `` 18 | 19 | 使用上面的这几种方式,你都需要伸出你的爪子,这会让你感到很不自在。使用`jk`会很棒,因为这两个按键正好就在你最强健有力的两个手指下面,并且你不用搞得好像在演奏吉他和弦似的移动手指。 20 | 21 | 有些人可能更喜欢使用`jj`,但我更钟意`jk`,有两个原因: 22 | 23 | * 使用两个不同的按键,你可以“滚动”你的手指而不是把同一个按键按两次。 24 | * 如果你出于习惯在常用模式下按了`jk`,只会将光标向下移动一下,然后又向上移动一下,最终光标还是停留在原来的位置。但是在常用模式下按了`jj`的话,只会把光标移动到一个不同的地方。 25 | 26 | 不过需要注意的是,如果在你所用的语言中`jk`会经常组合出现(例如德语),你可能就需要选择一个不同的mapping了。 27 | 28 | 学习Map 29 | ---------------- 30 | 31 | ok,现在你已经有了一个新的mapping,你会怎么学习使用它呢?特别是你已经用了这么久的escape按键,使用escape按键都已经刻入了你的脑子中,以至于在编辑的时候你会不假思索的敲击它。 32 | 33 | 重新学习一个mapping的窍门就是*强制*将之前的按键设置为不可用,*强迫*自己使用新的mapping。执行下面的命令: 34 | 35 | :::vim 36 | :inoremap 37 | 38 | 这个命令会告诉Vim在插入模式下敲击escape按键后执行``(no operation),这样escape按键在插入模式下就无效了。ok,现在你就*不得不*使用`jk`这个mapping来退出插入模式了。 39 | 40 | 一开始你可能不太适应,你还是会在插入模式下敲击escape按键,并且以为已经退出到了常用模式,然后开始敲击按键准备在常用模式下做一些事情,从而导致一些不需要的字符出现在你的文本中。这会让你感到有些不爽,但如果你坚持一段时间后,你会惊讶的发现你的思维和手指会多么快的适应 新的mapping。用不到一到两个小时你就不会再在插入模式下敲击escape了。 41 | 42 | 这个方法适用于所有的用于替代原有操作方式的新mapping,包括在生活中也是如此。当你想改掉一个坏习惯的时候,你最好能够想一些办法使得这个坏习惯很难甚至是不能发生。 43 | 44 | 如果你想学会自己做饭,不想每天都吃盖浇饭,那么每天下班的时候就不要去成都小吃了。这样你就会在饿了的时候想办法去做点东西吃,当前你先要确保你家里没有泡面。 45 | 46 | 如果你想戒烟,那你就不要把烟带在身上,把它放到车上。这样当你的烟瘾又犯了的时候,你会觉得走到车里去取烟是一件很蛋疼的事,这样你就不会吸了。 47 | 48 | 练习 49 | --------- 50 | 51 | 如果还是会在Vim的常用模式中使用方向键移动光标,那么就将它们映射为``。 52 | 53 | 如果你在编辑模式下也会使用方向键,同样的,映射为``吧。 54 | 55 | 正确使用Vim的关键就是使得自己能够快速的离开插入模式,然后在常用模式下进行移动。 56 | 57 | 58 | -------------------------------------------------------------------------------- /chapters/44.markdown: -------------------------------------------------------------------------------- 1 | 检测文件类型 2 | =================== 3 | 4 | 让我们创建一个Potion文件作为插件的测试样本。 5 | 6 | :::text 7 | factorial = (n): 8 | total = 1 9 | n to 1 (i): 10 | total *= i. 11 | total. 12 | 13 | 10 times (i): 14 | i string print 15 | '! is: ' print 16 | factorial (i) string print 17 | "\n" print. 18 | 19 | 这个代码创建了一个简单的阶乘函数并调用它10次,逐次输出结果。继续前进并用`potion factorial.pn`执行它。 20 | 输出结果应该像这样: 21 | 22 | :::text 23 | 0! is: 0 24 | 1! is: 1 25 | 2! is: 2 26 | 3! is: 6 27 | 4! is: 24 28 | 5! is: 120 29 | 6! is: 720 30 | 7! is: 5040 31 | 8! is: 40320 32 | 9! is: 362880 33 | 34 | 如果你得不到这个输出,或者你得到一个错误,停下来并排查问题所在。 35 | 这个代码应该会正常工作的。 36 | 37 | 这跟学习Vimscript没有关系,不过它能让你成为更好的程序猿。 38 | 39 | 检测Potion文件 40 | ---------------------- 41 | 42 | 用Vim打开`factorial.pn`并执行下面命令: 43 | 44 | :::vim 45 | :set filetype? 46 | 47 | Vim将显示`filetype=`,因为它还不认识`.pn`文件。让我们解决这个问题。 48 | 49 | 在你的插件的repo中创建`ftdetect/potion.vim`。在它里面加入下面几行: 50 | 51 | :::vim 52 | au BufNewFile,BufRead *.pn set filetype=potion 53 | 54 | 这创建了一个单行自动命令:一个设置`.pn`文件的filetype为`potion`的命令。很简明吧。 55 | 56 | 注意我们*没有*像之前经常做的那样使用一个自动命令组。 57 | Vim自动替你把`ftdetect/*.vim`文件包装成自动命令组,所以你不需要操心。 58 | 59 | 关闭`factorial.pn`并重新打开它。现在再执行前面的命令: 60 | 61 | :::vim 62 | :set filetype? 63 | 64 | 这次Vim显示`filetype=potion`。当Vim启动时,它加载`~/.vim/bundle/potion/ftdetect/potion.vim`里的自动命令组, 65 | 而当它打开`factorial.pn`时,自动命令起效,设置`filetype`为`potion`。 66 | 67 | 既然已经让Vim识别了Potion文件,我们可以继续前进来做些有用的东西了。 68 | 69 | 练习 70 | --------- 71 | 72 | 阅读`:help ft`。不要担心你看不懂里面的内容。 73 | 74 | 阅读`:help setfiletype`。 75 | 76 | 修改Potion插件中的`ftdetect/potion.vim`。 77 | 用`setfiletype`代替`set filetype`。 78 | -------------------------------------------------------------------------------- /acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | 鸣谢 2 | ==== 3 | 4 | 首先,我要感谢[Zed Shaw][]帮助我写作[笨方法学Vimscript][]并使之免费。 5 | 本书的写作格式及写作风格即受其激发。 6 | 7 | [Zed Shaw]: http://zedshaw.com/ 8 | [笨方法学Python]: http://learnpythonthehardway.org/ 9 | 10 | 同时感谢下列来自Github或Bitbucket的用户: 11 | 12 | * [aperiodic](https://github.com/aperiodic) 13 | * [billturner](https://github.com/billturner) 14 | * [chiphogg](https://github.com/chiphogg) 15 | * [ciwchris](https://github.com/ciwchris) 16 | * [cwarden](https://github.com/cwarden) 17 | * [dmedvinsky](https://github.com/dmedvinsky) 18 | * [flatcap](https://github.com/flatcap) 19 | * [helixbass](https://bitbucket.org/helixbass) 20 | * [hoelzro](https://github.com/hoelzro) 21 | * [jrib](https://github.com/jrib) 22 | * [lheiskan](https://github.com/lheiskan) 23 | * [lightningdb](https://github.com/lightningdb) 24 | * [manojkumarm](https://github.com/manojkumarm) 25 | * [manojkumarm](https://github.com/manojkumarm) 26 | * [markscholtz](https://github.com/markscholtz) 27 | * [marlun](https://github.com/marlun) 28 | * [mattsacks](https://github.com/mattsacks) 29 | * [Mr-Happy](https://github.com/Mr-Happy) 30 | * [mrgrubb](https://github.com/mrgrubb) 31 | * [NagatoPain](https://github.com/NagatoPain) 32 | * [nathanaelkane](https://github.com/nathanaelkane) 33 | * [nielsbom](https://github.com/nielsbom) 34 | * [nvie](https://github.com/nvie) 35 | * [Psycojoker](https://github.com/Psycojoker) 36 | * [riceissa](https://github.com/riceissa) 37 | * [rodnaph](https://github.com/rodnaph) 38 | * [rramsden](https://github.com/rramsden) 39 | * [sedm0784](https://github.com/sedm0784) 40 | * [sherrillmix](https://github.com/sherrillmix) 41 | * [tapichu](https://github.com/tapichu) 42 | * [ZyX-I](https://github.com/ZyX-I) 43 | 44 | 还有来自更多人的帮助,就不在此一一列举。 45 | -------------------------------------------------------------------------------- /chapters/27.markdown: -------------------------------------------------------------------------------- 1 | 字符串函数 2 | ================ 3 | 4 | Vim有许多内置(built-in)函数来操作字符串。本章中我们将介绍一些最为重要的字符串函数。 5 | 6 | 长度 7 | ------ 8 | 9 | 第一个介绍的函数是`strlen`。执行下面的命令: 10 | 11 | :::vim 12 | :echom strlen("foo") 13 | 14 | Vim显示`3`,也即`"foo"`的长度。现在尝试下面的命令: 15 | 16 | :::vim 17 | :echom len("foo") 18 | 19 | Vim再一次显示`3`。对于字符串,`len`和`strlen`有同样的效果。在本书稍后的章节我们会回过头来探讨`len`。 20 | 21 | 切割 22 | --------- 23 | 24 | 执行下面的命令(注意是`echo`而不是`echom`): 25 | 26 | :::vim 27 | :echo split("one two three") 28 | 29 | Vim显示`['one','two','three']`。`split`函数把字符串切割成列表。我们将简要介绍列表, 30 | 但现在不要纠结于此。 31 | 32 | 你也可以指定一个分隔符来代替"空白"。 33 | 34 | :::vim 35 | :echo split("one,two,three", ",") 36 | 37 | Vim再一次显示`['one','two','three']`,因为`split`的第二个参数是",",表示以","切割。 38 | 39 | 连接 40 | ------- 41 | 42 | 你不仅可以切割字符串,还可以连接它们。执行下面命令: 43 | 44 | :::vim 45 | :echo join(["foo", "bar"], "...") 46 | 47 | Vim将显示`foo...bar`。暂时不要在意列表语法。 48 | 49 | `split`和`join`可以合作无间。执行下面的命令: 50 | 51 | :::vim 52 | :echo join(split("foo bar"), ";") 53 | 54 | Vim显示`foo;bar`。首先我们把`"foo bar"`切割成列表,接着用分号作为分隔符把列表连接成字符串。 55 | 56 | 大小写转换 57 | -------------------- 58 | 59 | Vim有两个函数来转换字符串大小写。执行下面的命令: 60 | 61 | :::vim 62 | :echom tolower("Foo") 63 | :echom toupper("Foo") 64 | 65 | Vim显示`foo`和`FOO`。这很浅显易懂吧。 66 | 67 | 在许多语言(如Python)有一个惯例是在进行比较之前把字符串强制转换成小写来实现大小写无关的比较。 68 | 在Vimscript里不需要这么做,因为有大小写不敏感的比较运算符。如果你回忆不起来,重新读关于比较的那一章。 69 | (译注:如果你回忆不起来,那是第22章) 70 | 71 | 你可以自由选择使用`tolower`或`==#`以及`==?`来实现大小写敏感的比较。Vimscript社区对此还没有明显的偏好。 72 | 选定一个并在你所有的脚本中保持一致。 73 | 74 | 练习 75 | --------- 76 | 77 | 执行`:echo split('1 2')`和`:echo split('1,,,2',',')`。它们表现一致吗? 78 | 79 | 阅读`:help splt()`。 80 | 81 | 阅读`:help join()`。 82 | 83 | 阅读`:help functions`并浏览有关String的内置函数。使用`/`命令来辅助你(记住,Vim的帮助文件可以以一般文件的方式浏览)。 84 | 这里有着*许多*函数,所以不要纠结于每一个函数的文档。给自己留下印象,以便于将来的不时之用, 85 | 这就够了。 86 | -------------------------------------------------------------------------------- /chapters/09.markdown: -------------------------------------------------------------------------------- 1 | 更多的Mappings 2 | ============== 3 | 4 | 迄今为止我们已经说了很多mappings的内容,但现在我们要再次实践一下。mappings是 5 | 使得Vim编辑更为高效的方便快捷途径之一,有必要多加用心。 6 | 7 | 有个概念在多个例子中出现过,但是我们都没有明确解释,那就是多字符mappings的连续性。 8 | 9 | 运行如下命令: 10 | 11 | :::vim 12 | :nnoremap jk dd 13 | 14 | 确保你处于normal模式,快速输入`jk`。Vim会删除当前行。 15 | 16 | 现在试试先输入`j`,停顿一下。如果你输入`j`后没有快速输入`k`,Vim就会判定你不想 17 | 生效那个映射,而是将`j`按默认操作运行(下移一行)。 18 | 19 | 这个映射会给光标移动操作带来麻烦,我们先删除它。运行下面的命令: 20 | 21 | :::vim 22 | :nunmap jk 23 | 24 | 现在normal模式下快速输入`jk`会像往常一样下移一行然后又上移一行。 25 | 26 | 一个更为复杂的Mapping 27 | --------------------- 28 | 29 | 你已经见过很多简单的mappings了,是时候看看一些复杂的了。运行下面的命令: 30 | 31 | :::vim 32 | :nnoremap " viwa"hbi"lel 33 | 34 | 那是一个有趣的mappings!你自己可以先试试。进入normal模式,移动光标至一个单词, 35 | 输入`"`。Vim将那个单词用双引号包围! 36 | 37 | 它是如何工作的呢?我们拆分这个映射并逐个解释: 38 | 39 | :::text 40 | viwa"hbi"lel 41 | 42 | * `viw`: 高亮选中单词 43 | * ``: 退出visual模式,此时光标会在单词的最后一个字符上 44 | * `a`: 移动光标至当前位置之 *后* 并进入insert模式 45 | * `"`: 插入一个`"` 46 | * ``: 返回到normal模式 47 | * `h`: 左移一个字符 48 | * `b`: 移动光标至单词头部 49 | * `i`: 移动光标至当前位置之 *前* 并进入insert模式 50 | * `"`: 插入一个`"` 51 | * ``: 返回到normal模式 52 | * `l`: 右移一个字符,光标置于单词的头部 53 | * `e`: 移动光标至单词尾部 54 | * `l`: 右移一个字符,置光标位置在第一个添加的引号上 55 | 56 | 记住:因为我们使用的是`nnoremap`而不是`nmap`,所以尽管你映射了字符序列中的字符 57 | 也不会有影响。Vim会将其中的字符按默认功能执行。 58 | 59 | 希望你能看出Vim mappings的潜能及随之引发的阅读困难。 60 | 61 | Exercises 62 | --------- 63 | 64 | 像刚才一样创建一个mapping,用单引号而不是双引号。 65 | 66 | 试试用`vnoremap`添加一个mapping,使其能够用引号将你 *高亮选中* 的文本包裹。 67 | 你可能会需要`` `< ``和`` `> ``命令,所以先执行`` :help `< ``看看帮助文档。 68 | 69 | 将normal模式下的`H`映射为移动到当前行的首部。`h`是左移,所以你可以认为`H`是 70 | “加强版”的`h`、 71 | 72 | 将normal模式下的`L`映射为移动到当前行的尾部。`l`是右移,所以你可以认为`L`是 73 | “加强版”的`l`、 74 | 75 | 读取帮助文档`:help H`和`:help L`看看你覆盖了哪些命令。考虑考虑这会不会影响你。 76 | 77 | 将这些mappings添加到你的`~/.vimrc`文件中,确保用你的“编辑`~/.vimrc`”和“重读`~/.vimrc`” 78 | 映射操作~ 79 | -------------------------------------------------------------------------------- /chapters/55.markdown: -------------------------------------------------------------------------------- 1 | 发布 2 | ============ 3 | 4 | 现在你拥有了足够的Vimscript技能来打造能帮助许多人的Vim插件。 5 | 这一章涉及如何把你的插件发布在网上,以便人们获取,还有如何向潜在用户派小广告。 6 | 7 | 托管 8 | ------- 9 | 10 | 你需要做的第一件事是把你的插件放在网上,让其他人可以下载它。 11 | 最普遍的选择是放到[Vim官网的script版面][vimorg]。 12 | 13 | 你需要这个网站的一个免费账户。一旦你有了,你可以点击"Add Script"链接并填写表单。 14 | 到那里你就会明白了。 15 | 16 | 在过去的几年中有一个趋势,越来越多的插件托管在类似Bitbucket或GitHub的网络集市上。 17 | 这种情况可能由于两个因素。首先,Pathogen使得每一个被安装的插件的文件不需要放在单独的位置。 18 | 像Mercurial和Git这样的分布式版本控制系统以及像Bitbucket和GitHub这样的公共托管网站的崛起对此也有影响。 19 | 20 | 提供代码仓库对于想要用版本控制管理自己的dotfiles的人来说是十分方便的。 21 | Mercurial用户可以使用Mercurial的"subrepositories"来跟踪插件版本的变化, 22 | 而Git用户可以使用submodules(尽管只能对其他Git代码仓库起作用,这跟Mercurial的subrepo不一样)。 23 | 24 | 对你安装的每一个插件有一个完整的仓库,也使得当发现它们出现问题时debug更简单。 25 | 你可以使用blame, bisection或其他你的VCS提供的工具来找出哪里的问题。 26 | 如果你在自己的机器上有一个仓库,奉献fixes也会变得更简单。 27 | 28 | 希望你已经决定把你的插件代码仓库公开出来。无论你采用了哪家的服务,*至少*代码库需要能够被人们获取。 29 | 30 | [vimorg]: http://www.vim.org/scripts/ 31 | 32 | 文档 33 | ------------- 34 | 35 | 你已经用Vim自己的帮助文档格式透彻地给插件作了文档。但你的工作还没完成呢。 36 | 你还需要写出一个简介,包括下面几条: 37 | 38 | 1. 你的插件可以用来干什么? 39 | 2. 为什么用户想要用它? 40 | 3. 为什么它比同类的插件(如果有的话)要好? 41 | 4. 它遵循什么协议? 42 | 5. 一个到完整文档的链接,可以考虑借助[vim-doc][]网站进行渲染。 43 | 44 | 这些应该放在你的README文件(它将会显示在Bitbucket或GitHub的版本库的主页面),你也可以把它作为Vim.org上的插件描述。 45 | 46 | 包括一些屏幕截图总是一个好主意。作为一个文本编辑器不意味着Vim没有一个用户界面。 47 | 48 | [vim-doc]: http://vim-doc.heroku.com/ 49 | 50 | 贴小广告 51 | --------- 52 | 53 | 一旦你已经把插件部署到各个托管网站上,是时候向全世界宣传它的到来! 54 | 你可以在Twitter上向你的粉丝介绍,在Reddit的[/r/vim][rvim]版面推广它,在你的个人网站上写关于它的博客, 55 | 在[Vim邮件列表][vimml]上给新手们派小广告。 56 | 57 | 无论何时,当你推出自己创作的东西,你总会收到一些赞美和批评。 58 | 不要对不好的评价耿耿于怀。倾听他们的呼声,同时厚着脸皮,心态平和地对待作品中被指出的小瑕缺(不管对还是不对)。 59 | 没有什么是十全十美的,而且这就是Internet,所以如果你想保持快乐和激情,你需要拿得起放得下。 60 | 61 | [rvim]: http://reddit.com/r/vim/ 62 | [vimml]: http://www.vim.org/maillist.php 63 | 64 | 练习 65 | --------- 66 | 67 | 如果你还没有Vim.org账户,创建一个。 68 | 69 | 察看你喜欢的插件的READEME文件,看看它们是怎么组织起来的以及它们包含的信息。 70 | 71 | -------------------------------------------------------------------------------- /chapters/04.markdown: -------------------------------------------------------------------------------- 1 | 模式映射 2 | ======== 3 | 4 | 上一章中我们谈论了如何在Vim中映射按键。我们使用的命令`map`在normal模式下工作。 5 | 如果阅读本章之前你自己已经折腾了,可能会注意到这个映射在visual模式一样工作。 6 | 7 | 你可以使用`nmap`、`vmap`和`imap`命令分别指定映射仅在normal、visual、insert模式有效。 8 | 9 | 执行如下命令: 10 | 11 | :::vim 12 | :nmap \ dd 13 | 14 | 在normal模式下,按下`\`。Vim会删除当前行。 15 | 16 | 现在进入Visual模式,再次按下`\`。什么都不会发生,因为我们告诉了Vim这个映射仅在normal 17 | 模式下工作(同时`\`的默认行为是什么都不做)。 18 | 19 | 运行如下命令: 20 | 21 | :::vim 22 | :vmap \ U 23 | 24 | 进入visual模式并选中一些文字,按下`\`。Vim将把选中文本转换成大写格式。 25 | 26 | 分别在normal模式和visual模式测试`\`按键,注意不同模式下的效应。 27 | 28 | 增强记忆 29 | -------- 30 | 31 | 起初,将同样的按键根据当前模式的不同映射到不同的功能听起来很可怕。为什么每次按下 32 | 那个键之前都要停下想想我们现在是在什么模式?那样是不是更浪费时间? 33 | 34 | 实践中我们发现那真不是个问题。一旦你经常使用Vim,你就不会在意你按下的键了。 35 | 你会想“删除一行”,而不是“按`dd·”。你的手指和大脑都会记住你的映射,潜意识中你就会 36 | 按下那些映射按键。 37 | 38 | insert模式 39 | ---------- 40 | 41 | 现在我们已经知道如何在normal模式和visual模式下映射按键。现在我们谈谈insert模式下的 42 | 映射方法。运行如下命令: 43 | 44 | :::vim 45 | :imap dd 46 | 47 | 你可能猜想这个命令的作用是在insert模式下通过按键`Ctrl+d`删除整行。这个映射很实用, 48 | 因为你不必每次都要为了删除某些行而切回到normal模式。 49 | 50 | 好的我们试一下。它并不如我们想象那般工作,而仅仅是在文件中添加了两个`d`字符! 51 | 它压根就没用。 52 | 53 | 问题就在于Vim只按我们说的做。这个例子中,我们说:“当我按下``时,相当于我 54 | 按了两次`d`”。而当你在insert模式下,按下两次`d`的作用就是输入两个字符`d`。 55 | 56 | 要想让这个映射按我们的期望执行,我们需要更加明确的指令。修改映射并运行如下命令: 57 | 58 | :::vim 59 | :imap dd 60 | 61 | ``告诉Vim按下ESC按键,即退出insert模式。 62 | 63 | 现在再试试这个映射。它能够正常工作,但是注意你是如何回到normal模式的。这是因为我们 64 | 告诉Vim``要退出insert模式并删除一行,但是我们没有告诉它再回到insert模式。 65 | 66 | 运行如下命令,修复映射问题: 67 | 68 | :::vim 69 | :imap ddi 70 | 71 | 结尾的`i`告诉Vim进入insert模式,至此我们的映射才最终完成。 72 | 73 | 练习 74 | ---- 75 | 76 | 设置一个映射,当你在insert模式时,可以通过按下``将当前光标所在的单词转换成 77 | 大写格式。每次我写一个类似`MAX_CONNECTIONS_ALLOWED`这样很长的常量时都能感觉到这个 78 | 映射的实用性。因为这样我就可以以小写的格式敲写常量,然后用这个映射将其转成大写, 79 | 不必一直需要按着shift键。 80 | 81 | 将那个映射添加上到你的`~/.vimrc`文件中。 82 | 83 | 设置一个映射,当你在 *normal* 模式时,可以通过按下``将当前光标所在的单词转换成 84 | 大写格式。这个映射和上面那个有点区别,因为你必须要进入normal模式,也不需要结束时 85 | 切到insert模式。 86 | 87 | 将那个映射添加上到你的`~/.vimrc`文件中。 88 | -------------------------------------------------------------------------------- /chapters/02.markdown: -------------------------------------------------------------------------------- 1 | 设置选项 2 | ======== 3 | 4 | Vim拥有很多选项可以设置以改变其展现方式。 5 | 6 | 主要有两种选项:布尔选项(值为"on"或"off")和键值选项。 7 | 8 | 布尔选项 9 | -------- 10 | 11 | 执行如下命令: 12 | 13 | :::vim 14 | :set number 15 | 16 | 如果之前屏幕左侧没有显示行号,那么现在你就会看见行号。执行命令: 17 | 18 | :::vim 19 | :set nonumber 20 | 21 | 行号应该消失。`number`是一个布尔选项:可以off、可以on。通过`:set number`命令打开、 22 | `:set nonumber`命令关闭。 23 | 24 | 所有的布尔选项都是这种配置方法。`:set `打开选项、`:set no`关闭选项。 25 | 26 | 切换布尔选项 27 | ------------ 28 | 29 | 你可以"切换"布尔选项的值,即从开启切为关闭或从关闭切为开启。执行命令: 30 | 31 | :::vim 32 | :set number! 33 | 34 | 行号会再次显示出来。再次执行命令: 35 | 36 | :::vim 37 | :set number! 38 | 39 | 行号应该会再次消失。添加一个`!`(感叹号)至布尔选项后面就会切换对于选项的值。 40 | 41 | 查看选项当前值 42 | -------------- 43 | 44 | 你可以使用一个`?`符号向Vim获取一个选项的当前值。执行如下命令并查看每个命令的 45 | 返回结果: 46 | 47 | :::vim 48 | :set number 49 | :set number? 50 | :set nonumber 51 | :set number? 52 | 53 | 注意第一次`:set number?`命令返回的是`number`而第二次返回的是`nonumber`。 54 | 55 | 键值选项 56 | -------- 57 | 58 | 有些选项并不只有off或on两种状态,它们需要一个值。执行如下命令,查看返回结果: 59 | 60 | :::vim 61 | :set number 62 | :set numberwidth=10 63 | :set numberwidth=4 64 | :set numberwidth? 65 | 66 | `numberwidth`选项改变行号的列宽。你可以通过`:set =`命令改变 67 | 非布尔选项的选项值,并使用`:set ?`命令查看选项的当前值。 68 | 69 | 来看看一些常用选项的值: 70 | 71 | :::vim 72 | :set wrap? 73 | :set shiftround? 74 | :set matchtime? 75 | 76 | 一次性设置多个选项 77 | ------------------ 78 | 79 | 最后,你可以在一个`:set`命令中设置多个选项的值。试试如下命令: 80 | 81 | :::vim 82 | :set numberwidth=2 83 | :set nonumber 84 | :set number numberwidth=6 85 | 86 | 注意最后一个命令是如何一次性设置两个选项值的。 87 | 88 | 练习 89 | ---- 90 | 91 | 阅读`:help 'number'`(注意有单引号)帮助文档。 92 | 93 | 阅读`:help relativenumber`帮助文档。 94 | 95 | 阅读`:help numberwidth`帮助文档。 96 | 97 | 阅读`:help wrap`帮助文档。 98 | 99 | 阅读`:help shiftround`帮助文档。 100 | 101 | 阅读`:help matchtime`帮助文档。 102 | 103 | 按你自己的喜好在你的`~/.vimrc`文件中添加几个设置选项。 104 | -------------------------------------------------------------------------------- /chapters/25.markdown: -------------------------------------------------------------------------------- 1 | 数字 2 | ======= 3 | 4 | 现在是时候开始深入讨论你能用到的变量类型。首先从数值类型开始吧。 5 | 6 | Vimscript有两种数值类型:Number和Float。一个Number是32位带符号整数。一个Float是浮点数。 7 | 8 | 数字(Number)形式 9 | -------------- 10 | 11 | 你可以通过一些不同的方式设置Number的格式。执行下面的命令: 12 | 13 | :::vim 14 | :echom 100 15 | 16 | 没什么好惊讶的 -- Vim显示`100`。现在执行下面的命令: 17 | 18 | :::vim 19 | :echom 0xff 20 | 21 | 这次Vim显示`255`。你可以加`0x`或`0X`前缀来指定16进制的数字。现在执行下面的命令: 22 | 23 | :::vim 24 | :echom 010 25 | 26 | 你也可以加`0`前缀来使用八进制。不过由于容易混淆,用的时候要保持头脑清醒。尝试执行下面的命令: 27 | 28 | :::vim 29 | :echom 017 30 | :echom 019 31 | 32 | 第一个命令中,Vim将打印出`15`,因为`17`在八进制中等于十进制的`15`。 33 | 在第二个命令中,Vim把数字的进制当作十进制,即使它以`0`开头,因为它不可能是一个八进制数字。 34 | 35 | 因为Vim会一声不吭地处理掉这样的错误,我建议尽量避免使用八进制数字。 36 | 37 | 浮点数(Float)格式 38 | ------------- 39 | 40 | Float也可以用许多方式进行定制。执行下面的命令: 41 | 42 | :::vim 43 | :echo 100.1 44 | 45 | 注意这里我们使用了`echo`而不是更常用的`echom`。待会我会解释为什么这样做(译注:当然你现在可以试试看)。 46 | 47 | Vim如愿输出了`100.1`。你也可以指定指数形式。执行下面命令: 48 | 49 | :::vim 50 | :echo 5.45e+3 51 | 52 | Vim输出`5450.0`。也可以用负的指数。执行下面命令: 53 | 54 | :::vim 55 | :echo 15.45e-2 56 | 57 | Vim输出`0.1545`。在10的幂前面的`+`或`-`是可选的。如果没有,就默认为正数。执行下面的命令: 58 | 59 | :::vim 60 | :echo 15.3e9 61 | 62 | Vim将输出等价的`1.53e10`。小数点和小数点后面的数字是*必须要有*的。执行下面命令并看它为何出错: 63 | 64 | :::vim 65 | :echo 5e10 66 | 67 | 强制转换 68 | -------- 69 | 70 | 当你在运算,比较或其他操作中混合使用Number和Float类型,Vim将把Number转换成Float, 71 | 以Float格式作为结果。执行下面命令: 72 | 73 | :::vim 74 | :echo 2 * 2.0 75 | 76 | Vim输出`4.0`。 77 | 78 | 除法 79 | -------- 80 | 81 | 在两个Number之间的除法中,余数会被丢弃。执行下面命令: 82 | 83 | :::vim 84 | :echo 3 / 2 85 | 86 | Vim输出`1`。如果你希望Vim使用浮点数除法,至少有一个数字必须是Float, 87 | 这样剩下的数字也会被转换成浮点数。执行下面命令: 88 | 89 | :::vim 90 | :echo 3 / 2.0 91 | 92 | Vim输出`1.5`。`3`被强制转换成一个浮点数,然后运行了普通的浮点数除法。 93 | 94 | 练习 95 | --------- 96 | 97 | 阅读`:help Float`。什么情况下在Vimscript中不能用浮点数? 98 | 99 | 阅读`:help floating-point-precision`。这意味着你在写一个处理浮点数的Vim插件时需要注意什么? 100 | -------------------------------------------------------------------------------- /chapters/29.markdown: -------------------------------------------------------------------------------- 1 | Normal命令 2 | ====== 3 | 4 | 目前为止我们已经介绍了几个最为常用的Vimscript命令,但都跟日常中在normal模式下处理文本的方式无关。 5 | 有没有一种办法能把我们的脚本跟日常的文本编辑命令结合起来呢? 6 | 7 | 答案显然是肯定的。之前我们已经见过`normal`命令,是时候更详细地介绍它了。 8 | 执行下面的命令: 9 | 10 | :::vim 11 | :normal G 12 | 13 | Vim将把你的光标移到当前文件的最后一行,就像是在normal模式里按下`G`。现在执行下面命令: 14 | 15 | :::vim 16 | :normal ggdd 17 | 18 | Vim将移动到文件的第一行(`gg`)并删除它(`dd`)。 19 | 20 | `normal`命令简单地接受一串键值并当作是在normal模式下输入的。就是那么简单! 21 | 22 | 避免映射 23 | ----------------- 24 | 25 | 执行下面的命令来映射`G`键到别的东西: 26 | 27 | :::vim 28 | :nnoremap G dd 29 | 30 | 现在在normal模式按下`G`将删除一整行。试试这个命令: 31 | 32 | :::vim 33 | :normal G 34 | 35 | Vim将删除当前行。`normal`命令将顾及当前的所有映射。 36 | 37 | 这意味着我们需要给`normal`提供类似于`nnoremap`之于`nmap`的版本, 38 | 否则我们没法使用它——考虑到我们猜测不了用户的映射方式。 39 | 40 | 幸好Vim真的有这样的命令叫`normal!`。执行这个命令: 41 | 42 | :::vim 43 | :normal! G 44 | 45 | 这次Vim将移动光标到文件底部,即使`G`已经被映射了。 46 | 47 | 在写Vim脚本时,你应该*总是*使用`normal!`,*永不*使用`normal`。不要信任用户在`~/.vimrc`中的映射。 48 | 49 | 特殊字符 50 | ------------------ 51 | 52 | 如果你使用`normal!`一段时间了,就很可能注意到一个问题。试试下面的命令: 53 | 54 | :::vim 55 | :normal! /foo 56 | 57 | 第一眼看上去它应该会开始搜索`foo`,但你将看到它不会正常工作。 58 | 问题在于`normal!`不会解析像``那样的特殊字符序列。 59 | 60 | 于是,Vim认为你想要搜索字符串序列"foo",没有意识到你甚至按下了回车来进行搜索! 61 | (译注:原文为you even pressed return to perform the search! 按后文的意思应该是没有按下return,待问作者) 62 | 我们将在下一章讨论如何应对这个问题。 63 | 64 | 练习 65 | --------- 66 | 67 | 阅读`:help normal`。在最后部分,你将获得关于下一章主题的提示。 68 | 69 | 附加题 70 | ------------ 71 | 72 | 如果你还没准备好面对挑战,跳过这一节。如果你够胆,祝你好运! 73 | 74 | 重温`:help normal`关于undo的部分。尝试设计一个删除两行却能单独撤销每次删除的映射。 75 | 建议从`nnoremap d dddd`开始吧。 76 | 77 | 这次你并不真的需要`normal!`(`nnoremap`就够了), 78 | 但是它揭示了一点:有时阅读一个Vim命令的文档可以激发关于别的内容的奇思妙想。 79 | 80 | 如果你未尝使用过`helpgrep`命令,那就是时候用上它了。阅读`:help helpgrep`。 81 | 留心关于怎样在匹配内容中浏览的部分。 82 | 83 | 暂时先别纠结模式(patterns),我们很快就要谈到它们。 84 | 现在只需了解你可以用类似`foo.*bar`来查找文档中包括该正则模式的行。 85 | 86 | 不幸的是,`helpgrep`会不时给你带来挫折感,因为为了找到某些词,你需要懂得去搜索某些词。 87 | 我会帮你省下些无用功,这次你得查找到一种手工修改Vim的撤销序列的方法, 88 | 这样你映射的两个删除才能独立地撤销。 89 | 90 | 在以后你要灵活变通(pragmatic)。有时在你迷惘徘徊的时候,Google一下,你就知道。 91 | -------------------------------------------------------------------------------- /chapters/06.markdown: -------------------------------------------------------------------------------- 1 | Leaders 2 | ======= 3 | 4 | 我们已经学了一种不会让我们发狂的键盘映射方法,但是你可以注意到另外一个问题。 5 | 6 | 每次我们像`:nnoremap dd`这样映射一个按键都会覆盖掉``的原有功能。 7 | 如果哪天我们想用``了,怎么办? 8 | 9 | 有些按键你平常使用并不需要用到。你几乎永远不会用到`-`、 `H`、`L`、``、`` 10 | 和``这些按键的功能(当然,是在normal模式下)。依据你的工作方式,可能还有其他你 11 | 不会用到的按键。 12 | 13 | 这些按键都可以随意映射,但是只有这6个按键貌似不够吧。难道为Vim称道的可定制传说 14 | 有问题? 15 | 16 | 映射按键序列 17 | ------------ 18 | 19 | 不像Emacs,Vim可以映射多个按键。运行下面命令: 20 | 21 | :::vim 22 | :nnoremap -d dd 23 | :nnoremap -c ddO 24 | 25 | norma模式下快读敲入 `-d`或`-c`查看效果。第一个映射作用是删除一行,第二个是 26 | 删除一行并进入insert模式。 27 | 28 | 这就意味着你可以用一个你不常用的按键(如`-`)作为“前缀”,后接其它字符作为一个整体 29 | 进行映射。你需要多敲一个按键以执行这些映射,多一个按键而已,很容易就记住了。 30 | 31 | 如果你也认为这是个好方法,我可以告诉你,Vim已经支持这种机制。 32 | 33 | Leader 34 | ------ 35 | 36 | 我们称这个“前缀”为“leader”。你可以按你的喜好设置你的leader键。运行命令: 37 | 38 | :::vim 39 | :let mapleader = "-" 40 | 41 | 你可以替换`-`为你喜欢的按键。尽管会屏蔽一个有用的功能,但我个人使用的是`,`,因为这个键比较 42 | 比较容易按到。 43 | 44 | 当你创建新的映射时,你可以使用``表示“我设置的leader按键”。运行命令: 45 | 46 | :::vim 47 | :nnoremap d dd 48 | 49 | 现在试试按下你的leader按键和`d`。Vim会删除当前行。 50 | 51 | 然而为何每次都要繁琐的设置``?为什么创建映射时不直接敲入你的“前缀”按键? 52 | 原因主要有三个。 53 | 54 | 首先,你某天可能会想要更换你的“leader”。在一个地方定义它使得更方便更换它。 55 | 56 | 第二,其他人看你的`~/.vimrc`文件时,一旦看到``就能够立即知道你的用意。如果他们 57 | 喜欢你的`~/.vimrc`配置,即使他们使用不同的leader也可以简单的复制你的映射配置。 58 | 59 | 最后,许多Vim插件都会创建以``开头的映射。如果你已经设置了leader,你会更容易上手 60 | 使用那些插件。 61 | 62 | Local Leader 63 | ------------ 64 | 65 | Vim有另外一个“leader”成为“local leader“。这个leader用于那些只对某类文件 66 | (如Python文件、HTML文件)而设置的映射。 67 | 68 | 本书将在后续章节讲述如何为特定类型的文件创建映射,但你可以现在创建一个“localleader”: 69 | 70 | :::vim 71 | :let maplocalleader = "\\" 72 | 73 | 注意我们使用`\\`而不是`\`,因为`\`在Vimscript中是转义字符。我们将在后续章节 74 | 讲到这个。 75 | 76 | 现在你就可以在映射中使用``了,使用方法和``一样(当然, 77 | 你要使用另外一个前缀)。 78 | 79 | 如果你不喜欢反斜线,请随意更改它。 80 | 81 | 练习 82 | ---- 83 | 84 | 阅读`:help mapleader`。 85 | 86 | 阅读`:help maplocalleader`。 87 | 88 | 在你的`~/.vimrc`文件中设置`mapleader`和`maplocalleader`。 89 | 90 | 增加``前缀到之前章节中你添加到`~/.vimrc`文件中的映射命令, 91 | 防止那些映射覆盖了默认的按键作用。 92 | -------------------------------------------------------------------------------- /chapters/05.markdown: -------------------------------------------------------------------------------- 1 | 精确映射 2 | ======== 3 | 4 | 准备好,下面的内容会比较难以理解。 5 | 6 | 目前为止,我们已经使用`map`、`nmap`、`vmap`以及`imap`创建了实用的按键映射。 7 | 他们很方便,但是有个缺点。运行下面的命令: 8 | 9 | :::vim 10 | :nmap - dd 11 | :nmap \ - 12 | 13 | 试试按下`\`(在normal模式)。有什么现象? 14 | 15 | 当你按下`\`时,Vim会解释其为`-`。但是我们又映射了`-`!Vim会继续解析`-`为`dd`, 16 | 即它会删除整行。 17 | 18 | 你使用那些命令创建的映射可能会被Vim解释成 *其它* 的映射。乍一听这像是一个优点, 19 | 但实际上这很变态。解释原因之前,我们先用如下命令删除那些映射: 20 | 21 | :::vim 22 | :nunmap - 23 | :nunmap \ 24 | 25 | 递归 26 | ---- 27 | 28 | 运行命令: 29 | 30 | :::vim 31 | :nmap dd Ojddk 32 | 33 | 上面的命令看上去像是要映射`dd`为: 34 | 35 | * 在当前行之前添加新行 36 | * 退出insert模式 37 | * 向下移动一行 38 | * 删除当前行 39 | * 向上移动到新加的行 40 | 41 | 貌似这个映射的作用是“清除当前行”。但你可以试试。 42 | 43 | 当你按下`dd`后,Vim就不动了。按下``才可以继续,但是你的文件中会多出许多 44 | 空行!想想发生了什么? 45 | 46 | 这个映射实际上是 *递归* 的!当你按下`dd`后,Vim解释为: 47 | 48 | * `dd`存在映射,执行映射的内容。 49 | * 新建一行。 50 | * 退出insert模式。 51 | * 向下移动一行。 52 | * `dd`存在映射,执行映射的内容。 53 | * 新建一行。 54 | * 退出insert模式。 55 | * 向下移动一行。 56 | * `dd`存在映射,执行映射的内容。然后一直这样。 57 | 58 | 这个映射永远不会结束!删除这个可怕的映射再继续: 59 | 60 | :::vim 61 | :nunmap dd 62 | 63 | 负面影响 64 | -------- 65 | 66 | `*map`系列命令的一个缺点就是存在递归的危险。另外一个是如果你安装一个插件,插件 67 | 映射了同一个按键为不同的行为,两者冲突,有一个映射就无效了。 68 | 69 | 当安装一个新的插件时,可能你不会使用或记住每一个其创建的映射。即使你记住了,你还得 70 | 回看下你的`~/.vimrc`文件以确保你自定义的映射与插件创建的没有冲突。 71 | 72 | 这导致插件安装变得乏味,易于出错。肯定有个解决办法。 73 | 74 | 非递归映射 75 | ---------- 76 | 77 | Vim提供另一组映射命令,这些命令创建的映射在运行时 *不会* 进行递归。运行命令: 78 | 79 | :::vim 80 | :nmap x dd 81 | :nnoremap \ x 82 | 83 | 按下`\`看看有什么现象。 84 | 85 | 当你按下`\`时,Vim忽略了`x`的映射,仅按照`x`的默认操作执行。即删除当前光标下的字符 86 | 而不是删除整行。 87 | 88 | 每一个`*map`系列的命令都有个对应的`*noremap`命令,包括:`noremap`/`nnoremap`、 89 | `vnoremap`和`inoremap`。这些命令将不递归解释映射的内容。 90 | 91 | 该何时使用这些非递归的映射命令呢? 92 | 93 | 答案是: **任何时候** 。 94 | 95 | **是的,没开玩笑, *任何时候* 。** 96 | 97 | 在安装插件或添加新的自定义映射时使用`*map`系列命令纯属是给自己 *找* 麻烦。 98 | 多敲几个字符以确保这个问题不会发生,救自己于火海。 99 | 100 | 练习 101 | ---- 102 | 103 | 将之前章节中添加到`~/.vimrc`文件中的映射命令全部换成非递归版本。 104 | 105 | 读帮助文档`:help unmap`。 106 | -------------------------------------------------------------------------------- /chapters/34.markdown: -------------------------------------------------------------------------------- 1 | 实例研究:Grep运算符(Operator),第三部分 2 | ===================================== 3 | 4 | 我们新鲜出炉的"grep运算符"工作得很好,但是写Vimscript的目的,就是要体贴地改善你的用户的生活。 5 | 我们可以额外做两件事,让我们的运算符更加符合Vim生态圈的要求。 6 | 7 | 保护寄存器 8 | ---------------- 9 | 10 | 由于把文本复制到未命名寄存器中,我们破坏了之前在那里的内容。 11 | 12 | 这并不是我们的用户想要的,所以让我们在复制之前先保存寄存器中的内容并于最后重新加载。 13 | 修改代码成这样: 14 | 15 | :::vim 16 | nnoremap g :set operatorfunc=GrepOperatorg@ 17 | vnoremap g :call GrepOperator(visualmode()) 18 | 19 | function! GrepOperator(type) 20 | let saved_unnamed_register = @@ 21 | 22 | if a:type ==# 'v' 23 | normal! `y 24 | elseif a:type ==# 'char' 25 | normal! `[v`]y 26 | else 27 | return 28 | endif 29 | 30 | silent execute "grep! -R " . shellescape(@@) . " ." 31 | copen 32 | 33 | let @@ = saved_unnamed_register 34 | endfunction 35 | 36 | 我们在函数的开头和结尾加入了两个`let`语句。 37 | 第一个用一个变量保存`@@`中的内容,第二个则重新加载保存的内容。 38 | 39 | 保存并source文件。测试一下,复制一些文本,接着按下`giw`来执行运算符, 40 | 然后按下`p`来粘贴之前复制的文本。 41 | 42 | 当写Vim插件时,你*总是*应该尽量在修改之前保存原来的设置和寄存器值,并在之后加载回去。 43 | 这样你就避免了让用户陷入恐慌的可能。 44 | 45 | 命名空间 46 | ----------- 47 | 48 | 我们的脚本在全局命名空间中创建了函数`GrepOperator`。 49 | 这大概不算什么大问题,但当你写Vimscript的时候,事前以免万一远好过事后万分歉意。 50 | 51 | 仅需增加几行代码,我们就能避免污染全局命名空间。把代码修改成这样: 52 | 53 | :::vim 54 | nnoremap g :set operatorfunc=GrepOperatorg@ 55 | vnoremap g :call GrepOperator(visualmode()) 56 | 57 | function! s:GrepOperator(type) 58 | let saved_unnamed_register = @@ 59 | 60 | if a:type ==# 'v' 61 | normal! `y 62 | elseif a:type ==# 'char' 63 | normal! `[v`]y 64 | else 65 | return 66 | endif 67 | 68 | silent execute "grep! -R " . shellescape(@@) . " ." 69 | copen 70 | 71 | let @@ = saved_unnamed_register 72 | endfunction 73 | 74 | 脚本的前三行已经被改变了。首先,我们在函数名前增加前缀`s:`,这样它就会处于当前脚本的命名空间。 75 | 76 | 我们也修改了映射,在`GrepOperator`前面添上``,所以Vim才能找到这个函数。 77 | 如果我们不这样做,Vim会尝试在全局命名空间查找该函数,这是不可能找到的。 78 | 79 | 欢呼吧,我们的`grep-operator.vim`脚本不仅非常有用,而且是一个善解人意的Vimscript公民! 80 | 81 | 练习 82 | --------- 83 | 84 | 阅读`:help `。 85 | 86 | 享受一下,吃点零食犒劳自己。 87 | -------------------------------------------------------------------------------- /chapters/21.markdown: -------------------------------------------------------------------------------- 1 | 条件语句 2 | ============ 3 | 4 | 每种编程语言都有产生分支流程的方法,在Vimscript中,这是用`if`语句实现的。 5 | `if`语句是Vimscript中产生分支的基本方法。这里没有类似Ruby中的`unless`语句, 6 | 所以代码中所有的判断都需要用`if`实现。 7 | 8 | 在谈论Vim的`if`语句之前,我们需要花费额外的时间讲讲语法,这样可以在同一页里讲完它。 9 | 10 | 多行语句 11 | ------------------------ 12 | 13 | 有时你在一行里写不下所需的Vimscript。在讲到自动命令组时,我们已经遇到过这样的例子了。 14 | 这里是我们之前写过的代码: 15 | 16 | :::vim 17 | :augroup testgroup 18 | : autocmd BufWrite * :echom "Baz" 19 | :augroup END 20 | 21 | 在理想的情况下,你可以分开成三行来写。但在手工执行命令的时候,这样写就太冗长了。 22 | 其实,你可以用管道符(`|`)来隔开每一行。执行下面的命令: 23 | 24 | :::vim 25 | :echom "foo" | echom "bar" 26 | 27 | Vim会把它当作两个独立的命令。如果你看不到两行输出,执行`:messages`查看消息日志。 28 | 29 | 在本书的剩余部分,当你想手工执行一个命令,却对输入新行和冒号感到心烦时,试试用管道隔开, 30 | 在一行里写完。 31 | 32 | If的基本用法 33 | -------- 34 | 35 | 现在让我们回到正题上来,执行下面的命令: 36 | 37 | :::vim 38 | :if 1 39 | : echom "ONE" 40 | :endif 41 | 42 | Vim将显示`ONE`,因为整数`1`是"truthy"。现在执行下面命令: 43 | 44 | :::vim 45 | :if 0 46 | : echom "ZERO" 47 | :endif 48 | 49 | Vim将*不*显示`ZERO`,因为整数`0`是"falsy"。让我们看看对字符串是怎么处理的。执行下面命令: 50 | 51 | :::vim 52 | :if "something" 53 | : echom "INDEED" 54 | :endif 55 | 56 | 结果可能让你吃惊。Vim*不会*把非空字符串当作"truthy",所以什么也没有显示。 57 | 58 | 让我们打破沙锅问到底。执行下面的命令: 59 | 60 | :::vim 61 | :if "9024" 62 | : echom "WHAT?!" 63 | :endif 64 | 65 | 66 | 这次Vim*会*显示了!为什么会这样? 67 | 68 | 为了搞懂发生了什么,执行下面三个命令: 69 | 70 | :::vim 71 | :echom "hello" + 10 72 | :echom "10hello" + 10 73 | :echom "hello10" + 10 74 | 75 | 第一个命令使得Vim输出`10`,第二个命令输出`20`,第三个则又一次输出`10`! 76 | 77 | 在探究了所有的命令后,对于Vimscript我们可以得出结论: 78 | 79 | * 如有必要,Vim将强制转换变量(和字面量)的类型。在解析`10 + "20foo"`时,Vim将把 80 | `"20foo"`转换成一个整数(`20`)然后加到`10`上去。 81 | * 以一个数字开头的字符串会被强制转换成数字,否则会转换成`0` 82 | * 在所有的强制转换完成*后*,当`if`的判断条件等于非零整数时,Vim会执行`if`语句体。 83 | 84 | Else 和 Elseif 85 | --------------- 86 | 87 | Vim,像Python一样,支持"else"和"else if"分句。执行下面的命令: 88 | 89 | :::vim 90 | :if 0 91 | : echom "if" 92 | :elseif "nope!" 93 | : echom "elseif" 94 | :else 95 | : echom "finally!" 96 | :endif 97 | 98 | Vim输出`finally!`,因为前面的判断条件都等于0,而0代表falsy。 99 | 100 | 练习 101 | --------- 102 | 103 | 来一杯啤酒,安抚自己因Vim中的字符串强制转换而受伤的心。 104 | -------------------------------------------------------------------------------- /chapters/50.markdown: -------------------------------------------------------------------------------- 1 | 段移动原理 2 | ======================= 3 | 4 | 如果你未曾用过Vim的段移动命令 (`[[`, `]]`, `[]` and `][`),现在花上几秒读读它们的帮助文档。 5 | 也顺便读读`:help section`。 6 | 7 | 还是不懂?这不是什么问题,我第一次读这些的时候也是这样。 8 | 在写代码之前,我们先岔开来学习这些移动是怎么工作的,然后在下一章我们将使得我们的Potion插件支持它们。 9 | 10 | Nroff文件 11 | ----------- 12 | 13 | 四个"段移动"命令正如其字面上的含义,可以用来在文件的"段"之间移动。 14 | 15 | 这些命令默认为[nroff文件][]而设计。 16 | Nroff类似于LaTex或Markdown -- 它是用来写标记文本的(最终会生成UNIX man页面)。 17 | 18 | Nroff文件使用一组"macro"来定义"段头"。 19 | 比如,这里有个来自于`awk`man页面的例子: 20 | 21 | :::nroff 22 | .SH NAME *** 23 | awk \- pattern-directed scanning and processing language 24 | .SH SYNOPSIS *** 25 | .B awk 26 | [ 27 | .BI \-F 28 | .I fs 29 | ] 30 | [ 31 | .BI \-v 32 | .I var=value 33 | ] 34 | [ 35 | .I 'prog' 36 | | 37 | .BI \-f 38 | .I progfile 39 | ] 40 | [ 41 | .I file ... 42 | ] 43 | .SH DESCRIPTION *** 44 | .I Awk 45 | scans each input 46 | .I file 47 | for lines that match ... 48 | 49 | 以`.SH`开头的行就是段头。我用`***`把它们标记出来。 50 | 四个段移动命令将在段头行之间移动你的光标。 51 | 52 | Vim以`.`和nroff的段头符开始的任何行当做一个段头,*即使你编辑的不是nroff文件*! 53 | 54 | 你可以改变`sections`设置来改变段头符,但Vim依旧需要在行开头有一个点,而且段头符必须是成对的字符, 55 | 所以这样改对Potion文件不会有足够的灵活性。 56 | 57 | 括号 58 | ------ 59 | 60 | 段移动命令*也*查看另一样东西:一个打开或关闭的大括号(`{`或`}`)作为行的第一个字符。 61 | 62 | `[[`和`]]`查看开括号,而`[]`和`][`查看闭括号。 63 | 64 | 这额外的"行为"使得你可以在C风格语言的段之间轻松移动。 65 | 然而,这些规则也依旧没有顾及你正在编辑的文件类型! 66 | 67 | 加入下面内容到一个缓冲区里: 68 | 69 | :::text 70 | Test A B 71 | Test 72 | 73 | .SH Hello A B 74 | 75 | Test 76 | 77 | { A 78 | Test 79 | } B 80 | 81 | Test 82 | 83 | .H World A B 84 | 85 | Test 86 | Test A B 87 | 88 | 现在执行`:set filetype=basic`来告诉Vim这是一个BASIC文件,并尝试段移动命令。 89 | 90 | `[[`和`]]`命令将在标记为`A`的行之间移动,而`[]`和`][`将在标记为`B`的行之间移动。 91 | 92 | 这告诉我们,Vim总是用同样的两条规则来处理段移动,即使没有一条是起作用的(比如在BASIC中的情况)! 93 | 94 | [nroff files]: http://en.wikipedia.org/wiki/Nroff 95 | 96 | 练习 97 | --------- 98 | 99 | 再次阅读`:help section`,现在你应该可以理解段移动了。 100 | 101 | 也顺便读读`:help sections`吧。 102 | -------------------------------------------------------------------------------- /chapters/24.markdown: -------------------------------------------------------------------------------- 1 | 函数参数 2 | ================== 3 | 4 | 毫无疑问,Vimscript函数可以接受参数。执行下面的命令: 5 | 6 | :::vim 7 | :function DisplayName(name) 8 | : echom "Hello! My name is:" 9 | : echom a:name 10 | :endfunction 11 | 12 | 执行下面的函数: 13 | 14 | :::vim 15 | :call DisplayName("Your Name") 16 | 17 | Vim将显示两行:`Hello! My name is:` 和 `Your Name`。 18 | 19 | 注意我们传递给`echom`命令的参数前面的`a:`。这表示一个变量的作用域,在前几章(译注:第20章)我们曾讲过。 20 | 21 | 让我们试一下不带作用域前缀会怎么样。执行下面的命令: 22 | 23 | :::vim 24 | :function UnscopedDisplayName(name) 25 | : echom "Hello! My name is:" 26 | : echom name 27 | :endfunction 28 | :call UnscopedDisplayName("Your Name") 29 | 30 | 这次Vim抱怨说它找不到变量`name`。 31 | 32 | 在写需要参数的Vimscript函数的时候,你*总需要*给参数加上前缀`a:`,来告诉Vim去参数作用域查找。 33 | 34 | 可变参数 35 | ------- 36 | 37 | Vimscript函数可以设计为接受不定数目的参数,就像Javascript和Python中的一样。执行下面命令: 38 | 39 | :::vim 40 | :function Varg(...) 41 | : echom a:0 42 | : echom a:1 43 | : echo a:000 44 | :endfunction 45 | 46 | :call Varg("a", "b") 47 | 48 | 这个函数向我们展示了许多东西,让我们来逐一审视。 49 | 50 | 函数定义中的`...`说明这个函数可以接受任意数目的参数。就像Python函数中的`*args` 51 | 52 | 函数中的第一行为输出消息`a:0`,结果显示`2`。当你在Vim中定义了一个接受可变参数的函数, 53 | `a:0`将被设置为你额外给的参数数量(译注:注意是额外的参数数量)。 54 | 刚才我们传递了两个参数给`Varg`,所以Vim显示`2`。(译注:2 - 0 ==# 2) 55 | 56 | 第二行为输出`a:1`,结果显示`a`。你可以使用`a:1`,`a:2`等等来引用你的函数接受的每一个额外参数。 57 | 如果我们用的是`a:2`,Vim就会显示"b" 58 | 59 | 第三行有些费解。当一个函数可以接受可变参数,`a:000`将被设置为一个包括所有传递过来的额外参数的列表(list)。 60 | 我们还没有讲过列表,所以不要太纠结于此。你不能对列表使用`echom`,因而在这里用`echo`代替。 61 | 62 | 你也可以将可变参数和普通参数一起用。执行下面的命令: 63 | 64 | :::vim 65 | :function Varg2(foo, ...) 66 | : echom a:foo 67 | : echom a:0 68 | : echom a:1 69 | : echo a:000 70 | :endfunction 71 | 72 | :call Varg2("a", "b", "c") 73 | 74 | 我们可以看到Vim将`"a"`作为具名参数(named argument)`a:foo`的值,将余下的塞进可变参数列表中。 75 | 76 | 赋值 77 | ---------- 78 | 79 | 试试执行下面的命令: 80 | 81 | :::vim 82 | :function Assign(foo) 83 | : let a:foo = "Nope" 84 | : echom a:foo 85 | :endfunction 86 | 87 | :call Assign("test") 88 | 89 | Vim将抛出一个错误,因为你不能对参数变量重新赋值。现在执行下面的命令: 90 | 91 | :::vim 92 | :function AssignGood(foo) 93 | : let foo_tmp = a:foo 94 | : let foo_tmp = "Yep" 95 | : echom foo_tmp 96 | :endfunction 97 | 98 | :call AssignGood("test") 99 | 100 | 这次就可以了,Vim显示`Yep`。 101 | 102 | 练习 103 | --------- 104 | 105 | 阅读`:help function-argument`的前两段。 106 | 107 | 阅读`:help local-variables`。 108 | -------------------------------------------------------------------------------- /chapters/11.markdown: -------------------------------------------------------------------------------- 1 | 本地缓冲区的选项设置和映射 2 | ================================= 3 | 4 | 现在我们先花点时间复习一下我们已经谈论过的三个东西:映射(mappings),缩写(abbreviations)和选项设置(options),这个过程中会讲到一些新的东西。我们将在一个单一的缓冲区中同时设置它们。 5 | 6 | 这一章所讲到的东西会在下一章中真正的显示它们的作用,目前我们只需先打下基础。 7 | 8 | 在这一章中你需要在Vim中打开两个文件,两个文件是分开的。我先将它们命名为`foo`和`bar`,你可以随便对它们命名。然后为每个文件输入一些文字。 9 | 10 | 映射 11 | -------- 12 | 13 | 选择文件`foo`,然后执行下面的命令: 14 | 15 | :::vim 16 | :nnoremap d dd 17 | :nnoremap x dd 18 | 19 | 现在保持在文件`foo`下面,确保当前处于常用模式下,然后敲击`d`。Vim会删除一行。这个之前讲到过,没什么新鲜的。 20 | 21 | 仍然保持在文件`foo`下面,敲击`x`。Vim也会删除一行。这很正常,因为我们也将`x`映射到`dd`了。 22 | 23 | 现在切换到文件`bar`。在常用模式下敲击`d`。同样的,Vim会删除当前行,也没有什么新鲜的。 24 | 25 | ok,现在来点新鲜的:在文件`bar`下敲击`x`。 26 | 27 | Vim只删除了一个字符,而不是删除整个行! 28 | 为什么会这样? 29 | 30 | 第二个`nnoremap`命令中的``告诉Vim这个映射只在定义它的那个缓冲区中有效: 31 | 32 | 当你在`bar`文件下敲击`x`,Vim找不到一个跟它匹配的映射,它将会被解析了两个命令:``(这个什么都不会干)和 `x`(通常会删除一个字符)。 33 | 34 | 35 | 本地Leader 36 | ------------ 37 | 38 | 在这个例子中,`x`是一个本地缓冲区映射,不过这种定义方式并不合适。如果我们需要设定一个只会用于特定缓冲区的映射,一般会使用``,而不是``。 39 | 40 | 41 | 使用两种不同的leader按键就像设置了一种命名空间,这会帮助你保证所有不同的映射对你而言更加清晰直接。 42 | 43 | 但你在编写一个会被其他人用到的插件的时候,这点显得尤其重要。使用``来设置本地映射会防止你的插件覆盖别人用``设置的全局映射,因为他们可能已经对他们做设置的全局映射非常之习惯了。 44 | 45 | 设置 46 | -------- 47 | 48 | 在这本书的前面几个章节里,我们谈论了使用`set`来设置选项。有一些选项总是会适用于整个Vim,但是有些选项可以基于缓冲区进行设置。 49 | 50 | 切回到文件`foo`,执行下面的命令: 51 | 52 | :::vim 53 | :setlocal wrap 54 | 55 | 然后切换到文件`bar`,执行下面的命令: 56 | 57 | :::vim 58 | :setlocal nowrap 59 | 60 | 把你的Vim窗口调小一些,你会发现有些行在`foo`中会自动换行,而在`bar`中则不会。 61 | 62 | 63 | 让我们来测试下另外一个选项。切换到`foo`执行下面的命令: 64 | 65 | :::vim 66 | :setlocal number 67 | 68 | 现在切换到`bar`,然后执行下面的命令: 69 | 70 | :::vim 71 | :setlocal nonumber 72 | 73 | 现在在文件`foo`中会出现行号,而在`bar`则没有。 74 | 75 | 不是所有的选项都可以使用`setlocal`进行设置。如果你想知道某个特定的选项是否可以设置为本地选项,执行`:help`查看它的帮助文档。 76 | 77 | 对于本地选项如何*真正地*地工作,我说的有些简略。在练习中你会学到更多这方面的细节。 78 | 79 | 遮盖 80 | --------- 81 | 82 | ok,在开始下一节之前,我们先来看关于本地映射的一个非常有趣的特性。切换到文件`foo`,然后执行下面的命令: 83 | 84 | :::vim 85 | :nnoremap Q x 86 | :nnoremap Q dd 87 | 88 | 然后敲击`Q`,看看会发生什么? 89 | 90 | 当你敲击`Q`,Vim会执行第一个映射,而不是第二个,因为第一个映射比起第二个要显得*更具体*,这可以看成第二个映射被第一个映射遮盖了。 91 | 92 | 切换回文件`bar`,然后敲击`Q`,Vim会使用第二个映射。这是因为在这个缓冲区中第二个映射没有被第一个映射遮盖。 93 | 94 | 练习 95 | --------- 96 | 97 | 阅读`:help local-options`。 98 | 99 | 阅读`:help setlocal`。 100 | 101 | 阅读`:help map-local`。 102 | -------------------------------------------------------------------------------- /chapters/08.markdown: -------------------------------------------------------------------------------- 1 | Abbreviations 2 | ============= 3 | 4 | Vim有个称为"abbreviations"的特性,与映射有点类似,但是它用于insert、replace和 5 | command模式。这个特性灵活且强大,不过本节只会谈及最常用的用法。 6 | 7 | 本书只会讲述insert模式下的abbreviations。运行如下命令: 8 | 9 | :::vim 10 | :iabbrev adn and 11 | 12 | 进入insert模式并输入: 13 | 14 | :::text 15 | One adn two. 16 | 17 | 在输入`adn`之后输入空格键,Vim会将其替换为`and`。 18 | 19 | 诸如这样的输入纠错是abbreviations的一个很实用的用法。运行命令: 20 | 21 | :::vim 22 | :iabbrev waht what 23 | :iabbrev tehn then 24 | 25 | 再次进入insert模式并输入: 26 | 27 | :::text 28 | Well, I don't know waht we should do tehn. 29 | 30 | 注意 *两个* abbreviations的替换时机,第二个没有输入空格却也替换了。 31 | 32 | Keyword Characters 33 | ------------------ 34 | 35 | 紧跟一个abbreviation输入"non-keyword character"后Vim会替换那个abbreviation。 36 | "non-keyword character"指那些不在`iskeyword`选项中的字符。运行命令: 37 | 38 | :::vim 39 | :set iskeyword? 40 | 41 | 你将看到类似于`iskeyword=@,48-57,_,192-255`的结果。这个格式很复杂,但本质上 42 | "keyword characters"包含一下几种: 43 | 44 | * 下划线字符 (`_`). 45 | * 所有字母字符,包括大小写。 46 | * ASCII值在48到57之间的字符(数字0-9)。 47 | * ASCII值在192到255之间的字符(一些特殊ASCII字符)。 48 | 49 | 如果你想阅读这个选项格式的 *完整* 描述,你可以运行命令`:help isfname`,另外 50 | 阅读之前最好准备点吃的。 51 | 52 | 你只要记住输入非字母、数字、下划线的字符就会引发abbreviations替换。 53 | 54 | 更多关于abbreviations 55 | --------------------- 56 | 57 | Abbreviations不仅仅只能纠错笔误。我们可以加几个日常编辑中常用的abbreviations。 58 | 运行如下命令: 59 | 60 | :::vim 61 | :iabbrev @@ steve@stevelosh.com 62 | :iabbrev ccopy Copyright 2013 Steve Losh, all rights reserved. 63 | 64 | 随意更换我的名字和邮箱地址为你的,然后试试这两个abbreviations吧~ 65 | 66 | 这些abbreviations将你常用的一长串字符压缩至几个字符,省的每次都要那么麻烦。 67 | 68 | Why Not Use Mappings? 69 | 为什么不用Mappings? 70 | ------------------- 71 | 72 | 不错,abbreviations和mappings很像,但是他们的定位不同。看个例子: 73 | 74 | 运行命令: 75 | 76 | :::vim 77 | :inoremap ssig -- Steve Loshsteve@stevelosh.com 78 | 79 | 这个 *mapping* 用于快速插入你的签名。进入insert模式并输入`ssig`试试看。 80 | 81 | 看起来一切正常,但是还有个问题。进入insert模式并输入如下文字: 82 | 83 | :::text 84 | Larry Lessig wrote the book "Remix". 85 | 86 | 注意到Vim将Larry名字中的`ssig`也替换了!mappings不管被映射字符串的前后字符是什么-- 87 | 它只在文本中查找指定的字符串并替换他们。 88 | 89 | 运行下面的命令删除上面的mappings并用一个abbreviation替换它: 90 | 91 | :::vim 92 | :iunmap ssig 93 | :iabbrev ssig -- Steve Loshsteve@stevelosh.com 94 | 95 | 再次试试这个abbreviation。 96 | 97 | 这次Vim会注意`ssig`的前后字符,只会在需要的时候替换它。 98 | 99 | Exercises 100 | --------- 101 | 102 | 在你的`~/.vimrc`文件中为经常拼写错误的单词增加abbreviations配置。一定要使用 103 | 上一章中你创建的mappings来重新打开读取`~/.vimrc`文件。 104 | 105 | 为你的邮箱地址、博客网址、签名添加abbreviations配置。 106 | 107 | 为你经常输入的文本添加abbreviations配置。 108 | -------------------------------------------------------------------------------- /chapters/56.markdown: -------------------------------------------------------------------------------- 1 | 还剩下什么? 2 | ========= 3 | 4 | 如果已经读到了这里并且完成了所有的例子和练习,你现在对Vimscript基础的掌握就很牢固了。 5 | 不要担心,还有*许多*东西需要学呢! 6 | 7 | 如果你求知若渴,这里还有一些东西值得你去探索。 8 | 9 | 配色方案 10 | ------------- 11 | 12 | 在本书中我们给Potion文件添加了语法高亮。作为硬币的另一面,我们也可以创建配色方案来决定每种语法元素的颜色。 13 | 14 | 制作Vim的配色方案非常简单直白,甚至有点重复。阅读`:help highlgiht`来学习基础知识。 15 | 你可能想要看看一些内置的配色方案来看他们怎么组织文件的。 16 | 17 | 如果你渴望挑战,看看我自己的[灰太狼][]配色方案来了解我是怎么用Vimscript来为我简化定义及维护工作的。 18 | 注意"palette"字典和`HL`函数,它们动态地生成`highlight`命令。 19 | 20 | [灰太狼]: https://github.com/sjl/badwolf/blob/master/colors/badwolf.vim 21 | 22 | Command命令 23 | ------------------- 24 | 25 | 许多插件允许用户使用键映射和函数调用来交互,但有一些偏好使用Ex命令。 26 | 举个例子,[Fugitive][]插件创建类似`:Gbrowse`和`:Gdiff`并把调用它们的方式留给用户定制。 27 | 28 | 像这样的命令是通过`:command`命令创建的。阅读`:help user-commands`来学习怎样给自己制作一个。 29 | 你应该已经学会了足够的Vimscript知识来帮助自己理解Vim文档,并以此来学习新的命令。 30 | 31 | [Fugitive]: https://github.com/tpope/vim-fugitive 32 | 33 | 运行时路径 34 | ----------- 35 | 36 | 在本书中,关于Vim怎么加载某个文件时,我都是用"使用Pathogen"应付过去的。 37 | 鉴于你已经懂得了许多Vimscript知识,你可以阅读`:help runtimepath`并查看[Pathogen源代码][pathogen-src] 38 | 来找出幕后隐藏的真相。 39 | 40 | [pathogen-src]: https://github.com/tpope/vim-pathogen/blob/master/autoload/pathogen.vim 41 | 42 | Omnicomplete 43 | ------------ 44 | 45 | Vim提供了许多不同的方法来补全文本(浏览`:help ins-completion`)。 46 | 大多数都很简单,但其中最强大的是"omnicomplete", 47 | 它允许你调用一个自定义的Vimscript函数来决定你想到的各种补全方式。 48 | 49 | 当你决定对omnicomplete一探究竟,你可以从`:help omnifunc`和`:help coml-omni`开始你的征途。 50 | 51 | 编译器支持 52 | ---------------- 53 | 54 | 在我们的Potion插件中,我们创建了一些编译并执行Potion文件的映射。 55 | Vim提供了更深入的支持来跟编译器交互,包括解析编译器错误并生成一个整洁的列表让你跳转到对应的错误。 56 | 57 | 如果你对此感兴趣,你可以从通读整篇`:help quickfix.txt`开始深入。 58 | 不过,我得提醒你`errorformat`不适合心脏虚弱的人阅读。 59 | 60 | 其他语言 61 | --------------- 62 | 63 | 这本书专注于Vimscript,但Vim也提供了其他语言的接口,比如Python, Ruby, 和Lua。 64 | 这意味着如果不喜欢Vimscript,你可以使用其他语言拓展Vim。 65 | 66 | 当然还是需要了解Vimscript来编辑你的`~/.vimrc`,和理解Vim提供给其他语言的API。 67 | 但使用一个替代语言可能是从Vimscript的局限之处解放出来的好办法,尤其在写大型插件的时候。 68 | 69 | 如果你想了解更多用特定语言拓展Vim,查看下列对应的帮助文档: 70 | 71 | * `:help Python` 72 | * `:help Ruby` 73 | * `:help Lua` 74 | * `:help perl-using` 75 | * `:help MzScheme` 76 | 77 | Vim文档 78 | ------------------- 79 | 80 | 作为最后的部分,这里列出了一些Vim帮助条目,它们非常有用,有趣,有道理,或者仅仅是好玩(排名不分先后): 81 | 82 | * `:help various-motions` 83 | * `:help sign-support` 84 | * `:help virtualedit` 85 | * `:help map-alt-keys` 86 | * `:help error-messages` 87 | * `:help development` 88 | * `:help tips` 89 | * `:help 24.8` 90 | * `:help 24.9` 91 | * `:help usr_12.txt` 92 | * `:help usr_26.txt` 93 | * `:help usr_32.txt` 94 | * `:help usr_42.txt` 95 | 96 | 练习 97 | --------- 98 | 99 | 去为你想要的功能写一个Vim插件,向全世界分享你的成果! 100 | -------------------------------------------------------------------------------- /chapters/19.markdown: -------------------------------------------------------------------------------- 1 | 变量 2 | ========= 3 | 4 | 到目前为止我们已经讲完了单行命令。在本书后面的三分之一个章节中将会把Vim脚本当作一个*脚本语言*。这部分东西不会像前面的你学到的东西一样马上可以学以致用,不过这是为本书的最后一部分打基础,最后一部分会讲解创建一个插件所需要的各个方面的东西。 5 | 6 | 我们开始吧。我们首先要了解的是变量。执行下面的命令。 7 | 8 | :::vim 9 | :let foo = "bar" 10 | :echo foo 11 | 12 | Vim会显示`bar`。`foo`现在是一个变量,我们将一个字符串`"bar"`赋值给它。现在执行这些命令: 13 | 14 | :::vim 15 | :let foo = 42 16 | :echo foo 17 | 18 | Vim会显示`42`,因为我们将`foo`赋值为整型`42`。 19 | 20 | 从这些小例子似乎可以看出Vim脚本是动态类型的。事实并非如此,我们之后会说明。 21 | 22 | 作为变量的选项 23 | -------------------- 24 | 25 | 你可以通过一种特殊语法将*选项*作为变量来设置。执行下面的命令: 26 | 27 | :::vim 28 | :set textwidth=80 29 | :echo &textwidth 30 | 31 | Vim会显示`80`。在名称的前面加一个`&`符号是告诉Vim你正在引用这个选项,而不是在使用一个名称刚好相同的变量。 32 | 33 | 我们来看下Vim是怎么处理布尔选项的。执行下面的命令: 34 | 35 | :::vim 36 | :set nowrap 37 | :echo &wrap 38 | 39 | Vim显示`0`。然后再试试这些选项: 40 | 41 | :::vim 42 | :set wrap 43 | :echo &wrap 44 | 45 | 这次Vim会显示`1`。这些输出很明确提示Vim会将整型`0`当作"false",整型`1`当作"true"。我们可以更进一步假设Vim会将所有的非0值整型当作"truthy",而事实确实如此。 46 | 47 | 我们也可以使用`let`命令来*设置*作为变量的选项。执行下面的命令: 48 | 49 | :::vim 50 | :let &textwidth = 100 51 | :set textwidth? 52 | 53 | Vim会显示`textwidth=100`。 54 | 55 | 既然`set`可以搞定选项的设置,那我们为什么还要用`let`呢?执行下面的命令: 56 | 57 | :::vim 58 | :let &textwidth = &textwidth + 10 59 | :set textwidth? 60 | 61 | 这一次Vim显示`textwidth=110`。当你用`set`来设置某个选项,你只能给它设置一个常量值。当你使用`let`并将它作为一个变量来设置,你可以使用Vim脚本的所有强大之处来决定它的值。 62 | 63 | 本地选项 64 | ------------- 65 | 66 | 如果你想将某个选项作为变量来设置它的*本地*值,而不是*全局*值,你需要在变量名前面加前缀。 67 | 68 | 在两个分隔的窗口中分别打开两个文件。执行下面的命令: 69 | 70 | :::vim 71 | :let &l:number = 1 72 | 73 | 然后切换到另一文件,然后再执行下面的命令: 74 | 75 | :::vim 76 | :let &l:number = 0 77 | 78 | 注意第一个窗口会出现行号,而第二个没有。 79 | 80 | 作为变量的寄存器(Register) 81 | ---------------------- 82 | 83 | 你也可以将*寄存器*当作变量来读取和设置。执行下面的命令: 84 | 85 | :::vim 86 | :let @a = "hello!" 87 | 88 | 现在把光标放到文本中的某个地方然后敲击`"ap`。这个命令会告诉Vim“在这里粘贴寄存器`a`中的内容”。我们设置了这个寄存器的内容,所以Vim会将`hello!`粘贴到你的文本中。 89 | 90 | 还可以读寄存器的内容。执行下面的命令: 91 | 92 | :::vim 93 | :echo @a 94 | 95 | Vim会输出`hello!`。 96 | 97 | 在你的文件中选择一个单词然后用`y`复制,再执行下面的命令: 98 | 99 | :::vim 100 | :echo @" 101 | 102 | Vim会输出你刚才复制的单词。`"`寄存器是“未命名(unnamed)”寄存器,在复制的时候没有指定寄存器的文本都会放到这里。 103 | 104 | 在你的文件中执行搜索`/someword`,然后执行下面的命令: 105 | 106 | :::vim 107 | :echo @/ 108 | 109 | Vim会输出你刚刚使用的搜索模式。这样你就可以通过编程来读*和修改*当前的搜索模式,有些时候这会很有用。 110 | 111 | 练习 112 | --------- 113 | 114 | 检查你的`~/.vimrc`文件,然后将其中的一些`set`和`setlocal`命令替换为它们的`let`形式。记住布尔选项仍然需要被设置为某个值。 115 | 116 | 尝试将某个布尔选项设置为0和1之外的值,例如`wrap`。当你将它设置为一个不同的数字时会怎么样?如果设置为字符串又会是什么情况? 117 | 118 | 回到你的`~/.vimrc`文件,然后恢复所有的修改。在`set`可以搞定的时候,永远都不要用`let`,这是因为`let`更难于阅读。 119 | 120 | 阅读`:help registers`,然后看看你可以进行读和写的寄存器列表。 121 | -------------------------------------------------------------------------------- /chapters/22.markdown: -------------------------------------------------------------------------------- 1 | 比较 2 | =========== 3 | 4 | 我们已经学习了条件语句,但如果我们不能进行比较,`if`语句并不怎么有用。 5 | 当然Vim允许我们比较值的大小,只是不会像看上去那么一目了然。 6 | 7 | 执行下面的命令: 8 | 9 | :::vim 10 | :if 10 > 1 11 | : echom "foo" 12 | :endif 13 | 14 | 显然,Vim会显示`foo`。现在执行下面的命令: 15 | 16 | :::vim 17 | :if 10 > 2001 18 | : echom "bar" 19 | :endif 20 | 21 | Vim什么都不显示,因为`10`不比`2001`大。目前为止,一切正常。运行下面命令: 22 | 23 | :::vim 24 | :if 10 == 11 25 | : echom "first" 26 | :elseif 10 == 10 27 | : echom "second" 28 | :endif 29 | 30 | Vim显示`second`。没什么好惊讶的。让我们试试比较字符串。执行下面命令: 31 | 32 | :::vim 33 | :if "foo" == "bar" 34 | : echom "one" 35 | :elseif "foo" == "foo" 36 | : echom "two" 37 | :endif 38 | 39 | Vim输出`two`。还是没什么好惊讶的,所以我开头说的(译注:Vim的比较不像看上去那么直白)到底是指什么呢? 40 | 41 | 大小写敏感 42 | ---------------- 43 | 44 | 执行下面的命令: 45 | 46 | :::vim 47 | :set noignorecase 48 | :if "foo" == "FOO" 49 | : echom "vim is case insensitive" 50 | :elseif "foo" == "foo" 51 | : echom "vim is case sensitive" 52 | :endif 53 | 54 | Vim执行`elseif`分句,所以显然Vimscript是大小写敏感的。有道理,但没什么好震惊的。 55 | 现在执行下面命令: 56 | 57 | :::vim 58 | :set ignorecase 59 | :if "foo" == "FOO" 60 | : echom "no, it couldn't be" 61 | :elseif "foo" == "foo" 62 | : echom "this must be the one" 63 | :endif 64 | 65 | **啊!** 就在这里停下来。是的,你所见属实。 66 | 67 | **`==`的行为取决于用户的设置。** 68 | 69 | 我发誓我没忽悠你。你再试试看看。我没开玩笑,这不是我干的。 70 | 71 | 防御性编程 72 | ---------------- 73 | 74 | 这意味着什么?意味着在为别人开发插件时,你*不能*信任`==`。 75 | 一个不加包装的`==`*不能*出现在你的插件代码里。 76 | 77 | 这个建议就像是"`nmap` VS `nnoremap`"一样。*永远不要*猜测你的用户的配置。 78 | Vim既古老,又博大艰深。在写插件时,你*不得不*假定用户们的配置五花八门,千变万化。 79 | 80 | 所以怎样才能适应这荒谬的现实?好在Vim有额外两种比较操作符来处理这个问题。 81 | 82 | 执行下面的命令: 83 | 84 | :::vim 85 | :set noignorecase 86 | :if "foo" ==? "FOO" 87 | : echom "first" 88 | :elseif "foo" ==? "foo" 89 | : echom "second" 90 | :endif 91 | 92 | Vim显示`first`因为`==?`是"无论你怎么设都大小写*不敏感*"比较操作符。现在执行下面的命令: 93 | 94 | :::vim 95 | :set ignorecase 96 | :if "foo" ==# "FOO" 97 | : echom "one" 98 | :elseif "foo" ==# "foo" 99 | : echom "two" 100 | :endif 101 | 102 | Vim显示`two`因为`==#`是"无论你怎么设都大小写*敏感*"比较操作符。 103 | 104 | 故事的最后告诉我们一个道理:你应该*总是*用显式的大小写敏感或不敏感比较。 105 | 使用常规的形式是*错的*并且它*终究*会出错。打多一下就能拯救你自己于焦头烂额中。 106 | 107 | 当你比较整数时,这点小不同不会有什么影响。 108 | 不过,我还是建议每一次都使用大小写敏感的比较(即使不一定需要这么做),好过该用的时候*忘记*用了。 109 | 110 | 在比较整数时使用`==#`或`==?`都可以,而且将来一旦你改成字符串间的比较,它还会正确工作。 111 | 如果你真想用`==`比较整数也不是不行,不过要铭记,一旦被改成字符串间的比较,你需要修改比较操作符。 112 | 113 | 练习 114 | --------- 115 | 116 | 尝试`:set ignorecase`和`:set noignorecase`,看看在不同状态下比较的表现。 117 | 118 | 阅读`:help ignorecase`来看看为什么有的人设置了这个选项。 119 | 120 | 阅读`:help expr4`看看所有允许的比较操作符。 121 | -------------------------------------------------------------------------------- /chapters/45.markdown: -------------------------------------------------------------------------------- 1 | 基本语法高亮 2 | ========================= 3 | 4 | 既然已经移除前进路上的绊脚石,是时候开始为我们的Potion插件写下一些有用的代码。 5 | 我们将从一些简单的语法高亮开始。 6 | 7 | 在你的插件的repo中创建`syntax/potion.vim`。把下面的代码放到你的文件里: 8 | 9 | :::vim 10 | if exists("b:current_syntax") 11 | finish 12 | endif 13 | 14 | echom "Our syntax highlighting code will go here." 15 | 16 | let b:current_syntax = "potion" 17 | 18 | 关闭Vim,然后打开你的`factorial.pn`文件。 19 | 你也许或也许不能看到消息,取决于你是否有其他插件在该插件之后输出消息。 20 | 如果你执行`:message`,你将会看到这个文件的确已经加载了。 21 | 22 | **注意:** 每次我告诉你打开Potion文件,我是想要你在一个新的Vim窗口或进程里打开,而不是在一个分割或tab。 23 | 打开一个新的Vim窗口导致Vim为此重新加载你所有的插件,而打开一个分割则不会。 24 | 25 | 代码文件开头和结尾的那几行是一个惯用法,如果这个缓冲区的语法高亮已经启动了,那就无需重新加载。 26 | 27 | 高亮关键字 28 | --------------------- 29 | 30 | 在本章的剩下部分,我们将忽略文件开头和结尾的`if`和`let`防御墙。不要移除那几行,只是眼不见为净而已。 31 | 32 | 用下面的代码替换掉文件中的占位符`echom`: 33 | 34 | :::vim 35 | syntax keyword potionKeyword to times 36 | highlight link potionKeyword Keyword 37 | 38 | 关闭`factorial.pn`并重新打开它。`to`和`times`被高亮成你的配色方案中的关键字类型了! 39 | 40 | 这两行展示了Vim中的基本的语法高亮。为了高亮某个语法: 41 | 42 | * 你首先要用`syntax keyword`或相关命令(我们待会会提到),定义一组语法类型。 43 | * 然后你要把这组类型链接到高亮组(highlighting groups)。 44 | 一个高亮组是你在配色方案里定义的东西,比如"函数名应该是蓝色的"。 45 | 46 | 这可以让插件作者决定有意义的语法类型分组,然后链接到通用的高亮组。 47 | 这同时也让配色方案创作者决定通用的程序结构,而不需要考虑单独的语言。 48 | 49 | 除了在我们的玩具程序中用到的,Potion还有其他的关键字,所以让我们修改syntax文件来一并高亮它们。 50 | 51 | :::vim 52 | syntax keyword potionKeyword loop times to while 53 | syntax keyword potionKeyword if elsif else 54 | syntax keyword potionKeyword class return 55 | 56 | highlight link potionKeyword Keyword 57 | 58 | 首先要说的是:最后一行没有改掉。我们依然告诉Vim所有在`potionKeyword`中的内容应该作为`Keyword`高亮。 59 | 60 | 我们现在新增三行,每行都以`syntax keyword potionKeyword`开头。 61 | 这意味着多次执行这个命令不会*重置*语法类型分组 —— 而是扩增它!这使得你可以化整为零地定义分组。 62 | 63 | 怎样定义分组取决于你: 64 | 65 | * 你可以仅仅一行密密麻麻地写满所有的内容。 66 | * 你可以划分成几行,来满足每行80列的规则以便于阅读。 67 | * 你可以每一项都独占一行,来使得diff的结果更加清晰。 68 | * 你可以跟我在这里做的一样,把相关的项放在同一行。 69 | 70 | 高亮函数 71 | ---------------------- 72 | 73 | Vim的另一个高亮组是`Function`。这就来加入一些Potion的内置函数到我们的高亮文件。 74 | 把你的syntax文件修改成这样: 75 | 76 | :::vim 77 | syntax keyword potionKeyword loop times to while 78 | syntax keyword potionKeyword if elsif else 79 | syntax keyword potionKeyword class return 80 | 81 | syntax keyword potionFunction print join string 82 | 83 | highlight link potionKeyword Keyword 84 | highlight link potionFunction Function 85 | 86 | 关闭并重新打开`factorial.pn`,你将看到内置的Potion函数现在已经高亮了。 87 | 88 | 它的工作原理就跟关键字高亮一样。我们定义了新的语法类型分组并链接到不同的高亮组。 89 | 90 | 练习 91 | --------- 92 | 93 | 想一想为什么文件开头的`if exists`和结尾的`let`是有用的。如果你搞不懂,不要担心。 94 | 我也曾就这个问题问过Tim Pope。 95 | 96 | 浏览`:help syn-keyword`。注意提到`iskeyword`的部分。 97 | 98 | 阅读`:help iskeyword`. 99 | 100 | 阅读`:help group-name`来了解一些配色方案作者常用的通用高亮组。 101 | -------------------------------------------------------------------------------- /chapters/42.markdown: -------------------------------------------------------------------------------- 1 | 旧社会下的插件配置方式 2 | ============================== 3 | 4 | 我们需要讲到的第一件事是如何配置我们的插件。在过去,这会是一次混乱的折腾, 5 | 但现在我们有一个工具可以非常方便地安装Vim插件。 6 | 7 | 我们需要先过一下基本的配置方式,然后我们会讲到如何省下麻烦。 8 | 9 | 基本配置方式 10 | ------------ 11 | 12 | Vim支持把插件分割成多个文件。你可以在`~/.vim`下创建许多不同种类的文件夹来放置不同的内容。 13 | 14 | 我们现在将讲述其中最为重要的几个文件夹,但不会在上面花费太多时间。 15 | 当我们创造Potion插件时,我们会逐一认识它们的。 16 | 17 | 在我们继续前进之前,需要先确定一些用词规范。 18 | 19 | 我将用"插件"表示一大堆做一系列相关事情的Vimscript代码。 20 | 在Vim里,"插件(plugin)"有一个更专业的定义,它表示"`~/.vim/plugins/`下的一个文件"。 21 | 22 | 在大多数时间里,我将使用第一个定义。如果指的是第二个定义,我会特意指明。 23 | 24 | ~/.vim/colors/ 25 | -------------- 26 | 27 | Vim将会查找`~/.vim/colors/mycolors.vim`并执行它。 28 | 这个文件应该包括生成你的配色方案所需的一切Vimscript命令。 29 | 30 | 本书中,我们不会谈到配色方案。如果想创造属于自己的配色方案,你应该从一个现存的配色方案上改造出来。 31 | 记住,`:help`将与你常在。 32 | 33 | ~/.vim/plugin/ 34 | -------------- 35 | 36 | `~/.vim/plugin/`下的文件将在*每次*Vim启动的时候执行。 37 | 这里的文件包括那些无论何时,在启动Vim之后你就想加载的代码。 38 | 39 | ~/.vim/ftdetect/ 40 | ---------------- 41 | 42 | `~/.vim/ftdetect/`下的文件在每次你启动Vim的时候*也会*执行。 43 | 44 | `ftdetect`是"filetype detection"的缩写。 45 | 这里的文件*仅仅*负责启动检测和设置文件的`filetype`类型的自动命令。 46 | 这意味着它们一般不会超过一两行。 47 | 48 | ~/.vim/ftplugin/ 49 | ---------------- 50 | 51 | `~/.vim/ftplugin/`下的文件则各不相同。 52 | 53 | 一切皆取决于它的名字!当Vim把一个缓冲区的`filetype`设置成某个值时, 54 | 它会去查找`~/.vim/ftplugin/`下对应的文件。 55 | 比如:如果你执行`set filetype=derp`,Vim将查找`~/.vim/ftplugin/derp.vim`。 56 | 一旦文件存在,Vim将执行它。 57 | 58 | Vim也支持在`~/.vim/ftplugin/`下放置文件夹。 59 | 再以我们刚才的例子为例:`set filetype=derp`将告诉Vim去执行`~/.vim/ftplugin/derp/`下的全部`*.vim`文件。 60 | 这使得你可以按代码逻辑分割在`ftplugin`下的文件。 61 | 62 | 因为每次在一个缓冲区中执行`filetype`时都会执行这些文件,所以它们*只能*设置buffer-local选项! 63 | 如果在它们中设置了全局选项,所有打开的缓冲区的设置都会遭到覆盖! 64 | 65 | ~/.vim/indent/ 66 | -------------- 67 | 68 | `~/.vim/indent/`下的文件类似于`ftplugin`下的文件。加载时也是只加载名字对应的文件。 69 | 70 | `indent`文件应该设置跟对应文件类型相关的缩进,而且这些设置应该是buffer-local的。 71 | 72 | 是的,你当然可以把这些代码也一并放入`ftplugin`文件, 73 | 但最好把它们独立出来,让其他Vim用户理解你的意图。这只是一种惯例,不过请尽量体贴用户并遵从它。 74 | 75 | ~/.vim/compiler/ 76 | ---------------- 77 | 78 | `~/.vim/compiler`下的文件非常类似于`indent`文件。它们应该设置同类型名的当前缓冲区下的编译器相关选项。 79 | 80 | 不要担心不懂什么是"编译器相关选项"。我们等会会解释。 81 | 82 | ~/.vim/after/ 83 | ------------- 84 | 85 | `~/.vim/after`文件夹有点神奇。这个文件夹下的文件会在每次Vim启动的时候加载, 86 | 不过是在`~/.vim/plugin/`下的文件加载了*之后*。 87 | 88 | 这允许你覆盖Vim的默认设置。实际上你将很少需要这么做,所以不用理它, 89 | 除非你有"Vim设置了选项`x`,但我想要不同的设置"的主意。 90 | 91 | ~/.vim/autoload/ 92 | ---------------- 93 | 94 | `~/.vim/autoload`文件夹就更加神奇了。事实上它的作用没有听起来那么复杂。 95 | 96 | 简明扼要地说:`autoload`是一种延迟插件代码到需要时才加载的方法。 97 | 我们将在重构插件的时候详细讲解并展示它的用法。 98 | 99 | ~/.vim/doc/ 100 | ----------- 101 | 102 | 最后,`~/.vim/doc/`文件夹提供了一个你可以放置你的插件的文档的地方。 103 | Vim对文档的要求是多多益善(看看我们执行过的所有`:help`命令就知道),所以为你的插件写文档是重要的。 104 | 105 | 练习 106 | --------- 107 | 108 | 重读本章。我没开玩笑。确保你(大体上)明白我们讲过的每一个文件夹。 109 | 110 | 作为额外的加分,找一些你正在用的Vim插件看看它们如何组织代码文件。 111 | -------------------------------------------------------------------------------- /chapters/18.markdown: -------------------------------------------------------------------------------- 1 | 负责任的编码 2 | ================== 3 | 4 | 到目前为止我们已经介绍了一堆Vim命令,这可以让你可以快速自定义Vim。除了自动命令组外其他的命令都是单行的命令,你可以不费吹灰之力就把它们添加到你的`~/.vimrc`文件中。 5 | 6 | 这本书的下一部分我们会开始专注于Vim脚本编程,将其当作一个真正的编程语言对待,不过在此之前,我会先讲一些在编写大量的Vim脚本时需要注意的东西。 7 | 8 | 注释 9 | ---------- 10 | 11 | Vim脚本非常强大,但对于那些想进入这个领域的程序员而言,在最近几年它似乎逐渐变得像一个弯弯曲曲的迷宫,让进入的人找不到归路。 12 | 13 | Vim的选项和命令经常会比较简短生硬,并且难于阅读,另外处理兼容性问题也会增加代码的复杂度。编写一个插件并且允许用户自定义又会让复杂度更进一级。 14 | 15 | 在编写大量Vim脚本时要保持防御意识。要养成习惯添加注释说明某段代码是干什么的,如果有一个相关的帮助主题(help topic),最好在注释中说明! 16 | 17 | 这不仅会给你以后的维护带来方便,而且如果你将你的`~/.vimrc`文件分享到Bitbucket或者GitHub(强烈推荐你这么做),这些注释也会帮助其他的人理解你的脚本。 18 | 19 | 分组 20 | -------- 21 | 22 | 之前创建的映射可以让我们在使用Vim的同时方便快捷地编辑和加载`~/.vimrc`。不幸的是这会导致`~/.vimrc`中的代码快速增长以至失去控制,并且变得难于阅读浏览。 23 | 24 | 我们用于对付这种情况的方法是使用Vim的代码折叠功能,将多行代码组织起来的作为一个部分然后对这部分的代码进行折叠。如果你从来没有用过Vim的折叠功能,那么你现在应该尽快去瞄一瞄。很多人(包括我自己)都认为在日常编码工作中代码折叠是不可或缺的。 25 | 26 | 首先我们需要为Vim脚本文件设置折叠。在你的`~/.vimrc`文件中添加下面几行: 27 | 28 | :::vim 29 | augroup filetype_vim 30 | autocmd! 31 | autocmd FileType vim setlocal foldmethod=marker 32 | augroup END 33 | 34 | 这会告诉Vim对任何Vim脚本文件使用`marker`折叠方法。 35 | 36 | 现在在显示`~/.vimrc`文件的窗口中执行`:setlocal foldmethod=marker`。如果你不执行这个命令,你会发现加载`~/.vimrc`文件后没什么效果,这是因为Vim已经为这个文件设置了文件类型(FileType),而自动命令只会在设置文件类型的时候执行。这让你以后不需要手动来做这个事情。 37 | 38 | 现在在自动命令组开始和结束的地方添加两行,像下面这样: 39 | 40 | :::vim 41 | " Vimscript file settings ---------------------- {{{ 42 | augroup filetype_vim 43 | autocmd! 44 | autocmd FileType vim setlocal foldmethod=marker 45 | augroup END 46 | " }}} 47 | 48 | 切换到常用模式,将光标放到这些文字中的任意一行,然后敲击`za`。Vim会折叠从包含`{{{`的行到包含`}}}`的行之间的所有行。再敲击`za`会展开所有这些行。 49 | 50 | 刚开始你可能会觉得为了代码折叠而对源代码进行注释会有些不合理,我刚开始也这么想。对于大多数文件我现在仍然觉得这种做法并并不合适。因为不是所有人都使用相同的编辑器,所以在代码中添加的折叠注释对于那些不用Vim的人而言就像是噪音。 51 | 52 | 不过Vim脚本文件比较特殊,因为一个不用Vim的人不太可能会读你的代码,并且最重要的是如果不对代码进行分组处理,写着写着你就不知道写到哪里了,严重点可能会经脉尽断,吐血而亡。 53 | 54 | 先自己尝试尝试吧,说不定你会逐渐喜欢上它。 55 | 56 | 简短的名称(Short Names) 57 | ----------- 58 | 59 | 对于大多数命令和选项,Vim支持使用它们的缩写。例如,下面的两个命令做的事情完全一样: 60 | 61 | :::vim 62 | :setlocal wrap 63 | :setl wrap 64 | 65 | 我*强烈*提醒你不要在你的`~/.vimrc`或者是你编写的插件中使用这些缩写。Vim脚本对于初学者而言本来就已经够晦涩难懂了;从长远来看使用缩写只会使得它更难于阅读。即使*你*知道某个缩写的意思,其他人未必读得懂。 66 | 67 | 换句话说,缩写只在编码的过程中手动执行命令的时候会*很有用*。在你按了回车键以后,就没人会看到你输入什么了,这样你也没必要输入更多的字符。 68 | 69 | 练习 70 | --------- 71 | 72 | 检查你的`~/.vimrc`文件,将所有相关的行组织起来。你可以这么开头:“基本设置(Basic Settings)“,”文件类型相关设置(FileType-specific 73 | settings)”,“映射(Mappings)”,和“状态条(Status Line)”。然后在每个部分添加折叠标记和标题。 74 | 75 | 想想怎么让Vim在第一次打开`~/.vimrc`文件的时候自动折叠所有设置了折叠注释的行。阅读`:help foldlevelstart`你会知道怎么搞。 76 | 77 | 检查你的`~/.vimrc`文件,把所有的命令和选项的缩写改成全称。 78 | 79 | 检查你的`~/.vimrc`文件,确保里面没有什么敏感信息。然后创建一个git或者Mercurial仓库,再将`~/.vimrc`文件放到里面,然后将这个文件链接到`~/.vimrc`。 80 | 81 | 提交你刚才创建的仓库,并把它放到Bitbucket或者GitHub上,这样其他的人都可以看到和学习它。记住要经常提交和推送到仓库中,这样你所做的修改也会被记录下来。 82 | 83 | 如果你不只在一个机器上使用Vim,那你就可以克隆那个仓库,然后像之前一样将这个文件链接到`~/.vimrc`文件。这样你就可以在所有的机器上都使用同样的Vim配置了。 84 | -------------------------------------------------------------------------------- /chapters/43.markdown: -------------------------------------------------------------------------------- 1 | 新希望:用Pathogen配置插件 2 | ======================================= 3 | 4 | Vim的插件配置方式,在你仅仅添加一个文件来自定义自己的Vim体验时很合理, 5 | 但当你想要使用别人写的插件时,这种方式会导致一团糟。 6 | 7 | 在过去,要想使用别人写好的插件,你得下载所有文件并逐一正确地放置它们。 8 | 你也可能使用`zip`或`tar`来替你做放置的工作。 9 | 10 | 在这个过程中有些明显的问题: 11 | 12 | * 当你想更新插件的时候怎么办?你可以覆盖旧的文件, 13 | 但如果作者删除了某个文件,你怎么知道你要手工删除对应文件? 14 | * 假如有两个插件正好使用了同样的文件名(比如`utils.vim`或别的更大众的名字)呢? 15 | 有时你可以简单地重命名掉它,但如果它位于`autoload/`或别的名字相关的文件夹中呢? 16 | 你改掉文件名,就等于改掉插件。这一点也不好玩。 17 | 18 | 人们总结出一系列hacks来让事情变得简单些,比如Vimball。 19 | 幸运的是,我们不再需要忍受这些肮脏的hacks。 20 | [Tim Pope][]创造了著名的[Pathogen][]插件让管理大量插件变得轻松愉快, 21 | 只要插件作者神志清醒地安排好插件结构。(译注:现在推荐[vundle][]来代替Pathogen,前者支持使用git下载插件) 22 | 23 | 让我们了解一下Pathogen的工作方式,以及为了让我们的插件更加兼容,我们需要做的事。 24 | 25 | [Tim Pope]: http://tpo.pe/ 26 | [Pathogen]: http://www.vim.org/scripts/script.php?script_id=2332 27 | [vundle]: https://github.com/gmarik/vundle 28 | 29 | 运行时路径 30 | ----------- 31 | 32 | 当Vim在特殊的文件夹,比如`syntax/`,中查找文件时,它不仅仅只到单一的地方上查找。 33 | 就像Linux/Unix/BSD系统上的`PATH`,Vim设置`runtimepath`以便查找要加载的文件。 34 | 35 | 在你的桌面创建`colors`文件夹。在这个文件夹中创建一个叫`mycolor.vim`的文件(在本示例中你可以让它空着)。 36 | 打开Vim并执行这个命令: 37 | 38 | :::vim 39 | :color mycolor 40 | 41 | Vim将显示一个错误,因为它不懂得去你的桌面查找。现在执行这个命令: 42 | 43 | :::vim 44 | :set runtimepath=/Users/sjl/Desktop 45 | 46 | 当然,你得根据你的情况修改路径名。现在再尝试color命令: 47 | 48 | :::vim 49 | :color mycolor 50 | 51 | 这次Vim找到了`mycolor.vim`,所以它将不再报错。由于文件是空的,它事实上什么都没*做*, 52 | 但由于它不再报错,我们确信它找到了。 53 | 54 | Pathogen 55 | -------- 56 | 57 | Pathogen插件在你加载Vim的时候自动地把路径加到你的`runtimepath`中。 58 | 所有在`~/.vim/bundle/`下的文件夹将逐个加入到`runtimepath`。(译注:vundle也是这么做的) 59 | 60 | 这意味着每个`bundle/`下的文件夹应该包括部分或全部的标准的Vim插件文件夹,比如`colors/`和`syntax/`。 61 | 现在Vim可以从每个文件夹中加载文件,而且每个插件文件都独立于自己的文件夹中。 62 | 63 | 这么一来更新插件就轻松多了。你只需要整个移除旧的插件文件夹,并迎来新的版本。 64 | 如果你通过版本控制来管理`~/.vim`文件夹(你应该这么做), 65 | 你可以使用Mercurial的subrepo或Git的submodule功能来直接签出(checkout)每个插件的代码库, 66 | 然后用一个简单的`hg pull; hg update`或`git pull origin master`来更新。 67 | 68 | 成为Pathogen兼容的 69 | ------------------------- 70 | 71 | 我们计划让我们的用户通过Pathogen安装我们写的Potion插件。 72 | 我们需要做的:在插件的代码库里,放置我们的文件到正确的文件夹中。就是这么简单! 73 | 74 | 我们插件的代码库展开后看起来就像这样: 75 | 76 | :::text 77 | potion/ 78 | README 79 | LICENSE 80 | doc/ 81 | potion.txt 82 | ftdetect/ 83 | potion.vim 84 | ftplugin/ 85 | potion.vim 86 | syntax/ 87 | potion.vim 88 | ... etc ... 89 | 90 | 我们把它放置在GitHub或Bitbucket上,这样用户就能简单地clone它到`bundle/`,一切顺利! 91 | 92 | 练习 93 | --------- 94 | 95 | 如果你还没有安装[vnudle][],安装它。(译注:原文是安装[Pathogen][],但是没有必要啦) 96 | 97 | 给你的插件创建Mercurial或Git代码库,起名叫`potion`。 98 | 你可以把它放到你喜欢的地方,并链接到`~/.vim/bundle/potion/`或就把它直接放到`~/.vim/bindle/potion/`。 99 | 100 | 在代码库中创建`README`和`LICENSE`文件,然后commit。 101 | 102 | Push到Bitbucket或GitHub。 103 | 104 | 阅读`:help runtimepath`。 105 | -------------------------------------------------------------------------------- /chapters/23.markdown: -------------------------------------------------------------------------------- 1 | 函数 2 | ========= 3 | 4 | 一如大多数编程语言,Vimscript支持函数。让我们看看如何创建函数,然后再讨论它们的古怪之处。 5 | 6 | 执行下面的命令: 7 | 8 | :::vim 9 | :function meow() 10 | 11 | 你可能会认为这将定义函数`meow`。不幸的是,情况不是这样的,我们已经掉进了Vimscript其中的一个坑。 12 | 13 | **没有作用域限制的Vimscript函数必须以一个大写字母开头!** 14 | 15 | 即使你*真的*给函数限定了作用域(我们待会会谈到),你最好也用一个大写字母开头。 16 | 大多数Vimscript程序猿都是这么做的,所以不要破例。 17 | 18 | ok,是时候认真地定义一个函数了。执行下面的命令: 19 | 20 | :::vim 21 | :function Meow() 22 | : echom "Meow!" 23 | :endfunction 24 | 25 | 这次Vim愉快地定义了一个函数。让我们试试运行它: 26 | 27 | :::vim 28 | :call Meow() 29 | 30 | 不出所料,Vim显示`Meow!` 31 | 32 | 让我们试试令它返回一个值。执行下面的命令: 33 | 34 | :::vim 35 | :function GetMeow() 36 | : return "Meow String!" 37 | :endfunction 38 | 39 | 现在执行这个命令试试: 40 | 41 | :::vim 42 | :echom GetMeow() 43 | 44 | Vim将调用这个函数并把结果传递给`echom`,显示`Meow String!`。 45 | 46 | 调用函数 47 | ----------------- 48 | 49 | 我们已经看到,Vimscript里调用函数有两种不同的方法。 50 | 51 | 当你想直接调用一个函数时,使用`call`命令。执行下面命令: 52 | 53 | :::vim 54 | :call Meow() 55 | :call GetMeow() 56 | 57 | 第一个函数输出`Meow!`,然而第二个却没有任何输出。当你使用`call`时,返回值会被丢弃, 58 | 所以这种方法仅在函数具有副作用时才有用。 59 | 60 | 第二种方法是在表达式里调用函数。这次不需要使用`call`,你只需引用函数的名字。 61 | 执行下面的命令: 62 | 63 | :::vim 64 | :echom GetMeow() 65 | 66 | 正如我们见过的,这会调用`GetMeow`并把返回值传递给`echom`。 67 | 68 | 隐式返回 69 | ------------------ 70 | 71 | 执行下面的命令: 72 | 73 | :::vim 74 | :echom Meow() 75 | 76 | 这将会显示两行:`Meow!`和`0`。第一个显然来自于`Meow`内部的`echom`。第二个则告诉我们, 77 | 如果一个Vimscript函数不返回一个值,它隐式返回`0`。看我们可以利用这一点做什么。执行下面命令: 78 | 79 | :::vim 80 | :function TextwidthIsTooWide() 81 | : if &l:textwidth ># 80 82 | : return 1 83 | : endif 84 | :endfunction 85 | 86 | 这个函数涉及到我们之前学到的许多重要概念: 87 | 88 | * `if`语句 89 | * 将选项作为变量 90 | * 访问特定作用域里的选项变量 91 | * 大小写敏感的比较 92 | 93 | 如果你对以上内容感到陌生,最好翻到前几章温习一遍。 94 | 95 | 现在我们已经定义了一个函数,该函数告诉我们当前缓冲区的`textwidth`会不会设得‘太过宽’。 96 | (因为80字符的限制适用于除了HTML之外的任何代码文件) 97 | 98 | 现在让我们使用它。执行下面的命令: 99 | 100 | :::vim 101 | :set textwidth=80 102 | :if TextwidthIsTooWide() 103 | : echom "WARNING: Wide text!" 104 | :endif 105 | 106 | 在这里我们做了什么? 107 | 108 | * 一开始我们设置全局的`textwidth`为`80`。 109 | * 接着我们运行一个if语句判断`TextwidthIsTooWide()`是否为真。 110 | * 由于不满足条件,`if`语句体(译注:包括函数内的和函数外的)不会被执行。 111 | 112 | 因为我们没有显式返回一个值,Vim从函数中返回代表'falsy'的`0`。试试改变一下。运行下面的命令: 113 | 114 | :::vim 115 | :setlocal textwidth=100 116 | :if TextwidthIsTooWide() 117 | : echom "WARNING: Wide text!" 118 | :endif 119 | 120 | 这次函数中的`if`执行了它的语句体,返回`1`,并且我们手工输入的`if`语句也执行了*它*的语句体。 121 | 122 | 练习 123 | --------- 124 | 125 | 阅读`:help :call`。目前先忽略关于"范围"的内容。你可以传递多少参数给一个函数?感到惊讶不? 126 | 127 | 阅读`:help E124`第一自然段并找出你可以用哪些字符来命名函数。可以用下划线吗?点(Dashes)呢? 128 | 重音符号(Accented characters)?Unicode符号?如果读了文档还是搞不清楚,试一下看看。 129 | 130 | 阅读`:help return`。这个命令的缩写("short form")是什么?(我说了你千万不要用它) 131 | 在你的预期之内吗?如果不是,为什么? 132 | -------------------------------------------------------------------------------- /chapters/48.markdown: -------------------------------------------------------------------------------- 1 | 基本折叠 2 | ============= 3 | 4 | 如果从未在Vim里使用过代码折叠,你不知道你都错过了什么。 5 | 阅读`:help usr_28`并花费时间在日常工作中使用它。 6 | 一旦到了铭记于指的程度,你就可以继续本章了。 7 | 8 | 折叠类型 9 | ---------------- 10 | 11 | Vim支持六种不同的决定如何折叠你的文本的折叠类型。 12 | 13 | ### Manual 14 | 15 | 你手动创建折叠并且折叠将被Vim储存在内存中。 16 | 当你关闭Vim时,它们也将一并烟消云散,而下次你编辑文件时将不得不重新创建。 17 | 18 | 在你把它跟一些自定义的创建折叠的映射结合起来时,这种方式会很方便。 19 | 在本书中,我们不会这么做,但当你想这么做的时候,它会帮上忙。 20 | 21 | ### Marker 22 | 23 | Vim基于特定的字符组合折叠你的代码。 24 | 25 | 这些字符通常放置于注释中(比如`// {{{`), 26 | 不过在有些语言里,你可以使用该语言自己的语法代替,比如javascript的`{`和`}`。 27 | 28 | 纯粹为了你的编辑器,用注释割裂你的代码看上去有点丑,但好处是你可以定制特定的折叠。 29 | 如果你想以特定的方式组织一个大文件,这个类型将是非常棒的选择。 30 | 31 | ### Diff 32 | 33 | 在diff文件时使用该特定的折叠类型。我们不会讨论它,因为Vim会自动使用它。 34 | 35 | ### Expr 36 | 37 | 这让你可以用自定义的Vimscript来决定折叠的位置。它是最为强大的方式,不过也需要最繁重的工作。 38 | 下一章我们将讲到它。 39 | 40 | ### Indent 41 | 42 | Vim使用你的代码的缩进来折叠。同样缩进等级的代码折叠到一块,空行则被折叠到周围的行一起去。 43 | 44 | 这是最便捷的方式,因为你的代码已经缩进过了;你仅仅需要启动它。 45 | 这将是我们用来折叠Potion代码的第一种方式。 46 | 47 | Potion折叠 48 | -------------- 49 | 50 | 让我们再一次看一下Potion实例代码: 51 | 52 | :::text 53 | factorial = (n): 54 | total = 1 55 | n to 1 (i): 56 | total *= i. 57 | total. 58 | 59 | 10 times (i): 60 | i string print 61 | '! is: ' print 62 | factorial (i) string print 63 | "\n" print. 64 | 65 | 函数体和循环体已经缩进好了。这意味着我们可以不怎么费力就能实现一些基本的缩进。 66 | 67 | 在我们开始之前,在`total *= i`上添加一个注释,这样我们就有一个供测试的多行内部块。 68 | 你将在做练习的时候学到为什么我们需要这么做,但暂时先信任我。现在文件看上去就像这样: 69 | 70 | :::text 71 | factorial = (n): 72 | total = 1 73 | n to 1 (i): 74 | # Multiply the running total. 75 | total *= i. 76 | total. 77 | 78 | 10 times (i): 79 | i string print 80 | '! is: ' print 81 | factorial (i) string print 82 | "\n" print. 83 | 84 | 在你的Potion插件的版本库下创建一个`ftplugin`文件夹,然后在里面创建一个`potion`文件夹。 85 | 最后,在*`potion`文件夹*里面创建一个`folding.vim`文件。 86 | 87 | 不要忘了每次Vim设置一个buffer的`filetype`为`potion`时,它都会执行这个文件中的代码。 88 | (因为它位于一个叫`potion`的文件夹) 89 | 90 | 将所有的折叠相关的代码放在同一个文件显然是一个好主意,它能帮我们维护我们的插件的繁多的功能。 91 | 92 | 在这个文件中加入下面一行: 93 | 94 | :::vim 95 | setlocal foldmethod=indent 96 | 97 | 关闭Vim,重新打开`factoria.pn`。用`zR`,`zM`和`za`尝试折叠功能。 98 | 99 | 一行Vimscript代码就能带来一些有用的折叠!这真是太酷了! 100 | 101 | 你可能注意到`factorial`函数的内循环里面的那几行不能折叠,尽管它们缩进了。 102 | 为什么会这样? 103 | 104 | 事实上,在使用`indent`折叠时,Vim默认忽略以`#`字符开头的行。 105 | 这在编辑C文件时很有用(这时`#`表示一个预编译指令),但在编辑其他文件时不怎么有意义。 106 | 107 | 让我们在`ftplugin/potion/folding.vim`中添加多一行来修复问题: 108 | 109 | :::vim 110 | setlocal foldmethod=indent 111 | setlocal foldignore= 112 | 113 | 关闭并重新打开`factorial.pn`,现在内部块可以正常地折叠了。 114 | 115 | 练习 116 | --------- 117 | 118 | 阅读`:help foldmethod`. 119 | 120 | 阅读`:help fold-manual`. 121 | 122 | 阅读`:help fold-marker`和`:help foldmarker`. 123 | 124 | 阅读`:help fold-indent`. 125 | 126 | 阅读`:help fdl`和`:help foldlevelstart`. 127 | 128 | 阅读`:help foldminlines`. 129 | 130 | 阅读`:help foldignore`. 131 | 132 | -------------------------------------------------------------------------------- /chapters/37.markdown: -------------------------------------------------------------------------------- 1 | 字典 2 | ============ 3 | 4 | 我们讲到的最后一种Vimscript类型将是字典。 5 | Vimscript字典类似于Python中的dict,Ruby中的hash,和Javascript中的object。 6 | 7 | 字典用花括号创建。值是异质的,但*键会被强制转换成字符串*。就是这么简单,你没想到吧? 8 | 9 | 执行这个命令: 10 | 11 | :::vim 12 | :echo {'a': 1, 100: 'foo'} 13 | 14 | Vim显示`{'a':1,'100':'foo'}`,这说明Vimscript的确把键强制转换为字符串,同时保留值不变。 15 | 16 | Vimscript避免了Javascript标准的蠢笨之处,允许你在字典的最后一个元素后留下一个逗号。 17 | (译注:在Javascript的标准中,最后一个元素后面不能留下一个逗号。 18 | 但在Firefox里,留下那个逗号是允许的,不过这是Firefox的问题。) 19 | 执行下面的命令: 20 | 21 | :::vim 22 | :echo {'a': 1, 100: 'foo',} 23 | 24 | Vim再次显示`{'a':1,'100':'foo'}`(译注:结尾小逗号不见了)。你应该*总是*在字典里留下一个多余的逗号, 25 | *尤其*是当字典的定义跨越多行的时候,这样增加新项的时候将不容易犯错。 26 | 27 | 索引 28 | -------- 29 | 30 | 查找字典中的一个值的语法跟大多数语言是一样的。执行这个命令: 31 | 32 | :::vim 33 | :echo {'a': 1, 100: 'foo',}['a'] 34 | 35 | Vim显示`1`。试试使用不是字符串的索引: 36 | 37 | :::vim 38 | :echo {'a': 1, 100: 'foo',}[100] 39 | 40 | Vim会在查找之前把索引强制转换成字符串,因为键只能是字符串,这么做是合理的。 41 | 42 | 当键仅由字母,数字和/或下划线组成时,Vimscript也支持Javascript风格的"点"查找。 43 | 试试下面的命令: 44 | 45 | :::vim 46 | :echo {'a': 1, 100: 'foo',}.a 47 | :echo {'a': 1, 100: 'foo',}.100 48 | 49 | 两种情况下,Vim都显示了正确的元素。使用哪种索引字典的方式取决于你自己的偏好。 50 | 51 | 赋值和添加 52 | -------------------- 53 | 54 | 像对待变量一样赋值给字典中的项,就可以在字典中轻松地添加新的项。 55 | 56 | :::vim 57 | :let foo = {'a': 1} 58 | :let foo.a = 100 59 | :let foo.b = 200 60 | :echo foo 61 | 62 | Vim显示`{'a': 100, 'b': 200}`。赋值和添加一个新项的方式是一样的。 63 | 64 | 移除项 65 | ---------------- 66 | 67 | 有两种方法可以移除字典中的项。执行下面的命令: 68 | 69 | :::vim 70 | :let test = remove(foo, 'a') 71 | :unlet foo.b 72 | :echo foo 73 | :echo test 74 | 75 | Vim显示`{}`和`100`。`remove`函数将移除给定字典的给定键对应的项,并返回被移除的值。 76 | `unlet`命令也能移除字典中的项,只是不返回值。 77 | 78 | 你不能移除字典中不存在的项。试试执行这个命令: 79 | 80 | :::vim 81 | :unlet foo["asdf"] 82 | 83 | Vim抛出一个错误。 84 | 85 | 选择`remove`还是`unlet`很大程度上取决于个人偏好。如果非要我说,我推荐使用`remove`, 86 | 因为它比`unlet`更灵活。`remove`可以做任何`unlet`能做的事,反过来不成立。 87 | 所以选择`remove`可以一招鲜,吃遍天。 88 | 89 | 字典函数 90 | -------------------- 91 | 92 | 就像列表,Vim有许许多多内置的字典函数。执行下面的命令: 93 | 94 | :::vim 95 | :echom get({'a': 100}, 'a', 'default') 96 | :echom get({'a': 100}, 'b', 'default') 97 | 98 | Vim显示`100`和`default`,如同列表版本的`get`函数. 99 | 100 | 你也可以检查给定字典里是否有给定的键。执行这个命令: 101 | 102 | :::vim 103 | :echom has_key({'a': 100}, 'a') 104 | :echom has_key({'a': 100}, 'b') 105 | 106 | Vim显示`1`和`0`。不要忘了,Vimscript把`0`当作假而其他数字则是真。 107 | 108 | 你可以用`items`从一个字典中获取对应的键值对,执行这个命令: 109 | 110 | :::vim 111 | :echo items({'a': 100, 'b': 200}) 112 | 113 | Vim将显示`[['a',100],['b',200]]`这样的嵌套列表。到目前为止,Vimscript字典*不一定*是有序的, 114 | 所以不要指望`items`的返回结果是有序的! 115 | 116 | 你可以用`keys`返回字典的所有的键和`values`返回所有的值。它们的作用一如其名——你可以查一下。 117 | 118 | 练习 119 | --------- 120 | 121 | 阅读`:help Dictionary`。看完它。注意大写`D`。 122 | 123 | 阅读`:help get()`. 124 | 125 | 阅读`:help has_key()`. 126 | 127 | 阅读`:help items()`. 128 | 129 | 阅读`:help keys()`. 130 | 131 | 阅读`:help values()`. 132 | -------------------------------------------------------------------------------- /chapters/14.markdown: -------------------------------------------------------------------------------- 1 | 自动命令组 2 | ================== 3 | 4 | 前面几章我们学习了自动命令。执行下面命令: 5 | 6 | :::vim 7 | :autocmd BufWrite * :echom "Writing buffer!" 8 | 9 | 现在使用`:write`命令将当前缓冲区写入文件,然后执行`:messages`命令查看消息日志。你会看到`Writing buffer!`在消息列表中。 10 | 11 | 然后将当前缓冲区写入文件,执行`:messages`查看消息日志。你会看到`Writing buffer!`在消息列表中出现了两次。 12 | 13 | 现在再次执行上面的自动命令: 14 | 15 | :::vim 16 | :autocmd BufWrite * :echom "Writing buffer!" 17 | 18 | 再次将当前缓冲区写入文件并执行`:messages`命令。你会看到`Writing buffer!`在消息列表中出现了*4*次,这是怎么回事? 19 | 20 | 这是因为当你以上面的方式创建第二个自动命令的时候,Vim没办法知道你是想替换第一个自动命令。在上面的示例中,Vim创建了两个*不同*的自动命令,并且这两个命令刚好做同样的事情。 21 | 22 | 这会有什么问题? 23 | ----------- 24 | 25 | 既然你现在知道了Vim可能创建两个完全一样的自动命令,你可能会想:“有什么大不了?只要别这么干就可以!”。 26 | 27 | 问题是当你加载你的`~/.vimrc`文件的时候,Vim会重新读取整个文件,包括你所定义的任何自动命令!这就意味着每次你加载你的`~/.vimrc`文件的时候,Vim都会复制之前的自动命令,这会降低Vim的运行速度,因为它会一次又一次地执行相同的命令。 28 | 29 | 30 | 你可以执行下面的命令模拟这种情况: 31 | 32 | :::vim 33 | :autocmd BufWrite * :sleep 200m 34 | 35 | 现在将当前缓冲区写入文件。你可能注意到Vim在写入文件的时候有点缓慢,当然也你可能注意不到。现在执行上面的自动命令三次: 36 | 37 | :::vim 38 | :autocmd BufWrite * :sleep 200m 39 | :autocmd BufWrite * :sleep 200m 40 | :autocmd BufWrite * :sleep 200m 41 | 42 | 再次写文件。这次会更明显。 43 | 44 | 当然你不会创建任何只是进行sleep而不做任何事情的自动命令,不过一个使用Vim的老鸟的`~/.vimrc`文件可以轻易达到1000行,其中会有很多自动命令。再加上安装的插件中的自动命令,这肯定会影响Vim的速度。 45 | 46 | 把自动命令放到组中(Grouping Autocommands) 47 | --------------------- 48 | 49 | 对于这个问题,Vim有一个解决方案。这个解决方案的第一步是将相关的自动命令收集起来放到一个已命名的组(groups)中。 50 | 51 | 新开一个Vim实例,这样可以清除之前所创建的自动命令。然后运行下面的命令: 52 | 53 | :::vim 54 | :augroup testgroup 55 | : autocmd BufWrite * :echom "Foo" 56 | : autocmd BufWrite * :echom "Bar" 57 | :augroup END 58 | 59 | 中间两行的缩进没有什么含义,如果你不想输入的话可以不输。 60 | 61 | 将一个缓冲区写入文件然后执行`:messages`。你应该可以在消息日志列表中看到`Foo`和`Bar`。现在执行下面的命令: 62 | 63 | :::vim 64 | :augroup testgroup 65 | : autocmd BufWrite * :echom "Baz" 66 | :augroup END 67 | 68 | 当你再次将缓冲区写入文件的时候猜猜会发生什么。ok,你也许已经有结果了,重新写入缓冲区,然后执行`:messages`命令,看看你猜对了没。 69 | 70 | 清除自动命令组 71 | --------------- 72 | 73 | 当你写入文件的时候发生什么了?猜对了么? 74 | 75 | 如果你认为Vim会替换那个组,那么你猜错了。不要紧,很多人刚开始的时候都会这么想(我也是)。 76 | 77 | 当你多次使用`augroup`的时候,Vim每次都会组合那些组。 78 | 79 | 如果你想清除一个组,你可以把`autocmd!`这个命令包含在组里面。执行下面的命令: 80 | 81 | :::vim 82 | :augroup testgroup 83 | : autocmd! 84 | : autocmd BufWrite * :echom "Cats" 85 | :augroup END 86 | 87 | 现在试试写入文件然后执行`:messages`查看消息日志。这次Vim只会输出`Cats`在消息列表中。 88 | 89 | 在Vimrc中使用自动命令 90 | -------------------------------- 91 | 92 | 既然我们现在知道了怎么把自动命令放到一个组里面以及怎么清除这些组,我们可以使用这种方式将自动命令添加到`~/.vimrc`中,这样每次加载它的时候就不会复制自动命令了。 93 | 94 | 添加下面的命令到你的`~/.vimrc`文件中: 95 | 96 | :::vim 97 | augroup filetype_html 98 | autocmd! 99 | autocmd FileType html nnoremap f Vatzf 100 | augroup END 101 | 102 | 当进入`filetype_html`这个组的时候,我们会立即清除这个组,然后定义一个自动命令,然后退出这个组。当我们再次加载`~/.vimrc`文件的时候,清除组命令会阻止Vim添加一个一模一样的自动命令。 103 | 104 | 练习 105 | --------- 106 | 107 | 查看你的`~/.vimrc`文件,然后把所有的自动命令用上面组的方式包裹起来。如果你觉得有必要,可以把多个自动命令放到一个组里面。 108 | 109 | 想想上一节的示例中的自动命令是干啥的。 110 | 111 | 阅读`:help autocmd-groups`。 112 | 113 | -------------------------------------------------------------------------------- /chapters/17.markdown: -------------------------------------------------------------------------------- 1 | 状态条 2 | ============ 3 | 4 | Vim允许自定义每个窗口底部的状态条显示的文字,你可以通过设置`statusline`选项来进行自定义。执行下面的命令: 5 | 6 | :::vim 7 | :set statusline=%f 8 | 9 | 你可以在状态条上看到当前所编辑文件的路径(相对于当前路径)。再执行这个命令: 10 | 11 | :::vim 12 | :set statusline=%f\ -\ FileType:\ %y 13 | 14 | 现在你可以在状态条中看到类似`foo.markdown - FileType: [markdown]`这样的文字。 15 | 16 | 如果你熟悉C语言中的`printf`或者Python的字符串插值,那么这个选项的格式看起来会比较眼熟。如果不熟悉,你只需要记住以`%`开头的字符串会被展开为不同的文字,这取决于`%`后面的字符。在上面的示例中,`%f`会被替换为文件名,`%y`会被替换为文件类型。 17 | 18 | 注意状态条中的空格需要反斜线进行转义,这是因为`set`可以同时设置多个选项,这些选项会用空格分隔,我们在第二章讲过这个。 19 | 20 | 状态条设置可以很快变得非常复杂,不过有一个更好的办法来设置它们以至于让它们看起来更清晰。执行下面的命令: 21 | 22 | :::vim 23 | :set statusline=%f " 文件的路径 24 | :set statusline+=\ -\ " 分隔符 25 | :set statusline+=FileType: " 标签 26 | :set statusline+=%y " 文件的类型 27 | 28 | 第一个命令使用`=`来设置状态条只显示文件名,从而将之前的所有会在状态条中显示的值都屏蔽掉。之后再使用`+=`逐渐添加其他要显示的内容,一次添加一条。同时还使用注释来说明每一条所表示的含义以方便其他的人阅读我们的代码(也会方便我们自己以后阅读)。 29 | 30 | 执行下面的命令: 31 | 32 | :::vim 33 | :set statusline=%l " 当前行号 34 | :set statusline+=/ " 分隔符 35 | :set statusline+=%L " 总行数 36 | 37 | 现在状态条只包含当前所在行以及文件的总行数,并且显示成`12/223`这个样子。 38 | 39 | 宽度和边距 40 | ----------------- 41 | 42 | 可以在`%`后面添加额外的字符来改变状态条中信息的显示样式。执行下面的命令: 43 | 44 | :::vim 45 | :set statusline=[%4l] 46 | 47 | 现在状态条中的文件行数会至少显示为4个字符的宽度(例如:`[ 12]`),这可以用于防止状态条中的文字总是令人厌烦地跳来跳去。 48 | 49 | 默认情况下在值的左边添加边距。执行下面的命令: 50 | 51 | :::vim 52 | :set statusline=Current:\ %4l\ Total:\ %4L 53 | 54 | 你的状态条看来会是这个样子: 55 | 56 | :::text 57 | Current: 12 Total: 223 58 | 59 | 你可以使用`-`将边距添加在右边,而不是左边。执行下面的命令: 60 | 61 | :::vim 62 | :set statusline=Current:\ %-4l\ Total:\ %-4L 63 | 64 | 现在你的状态条看起来会是这个样子: 65 | 66 | :::text 67 | Current: 12 Total: 223 68 | 69 | 这样就好看多了,因为数字值是紧挨着它的标签的。 70 | 71 | 对于会被显示为数字的代码,你可以让Vim使用0代替空格来填充边距。执行下面的命令: 72 | 73 | :::vim 74 | :set statusline=%04l 75 | 76 | 现在当光标位于第12行的时候你的状态条会显示`0012`。 77 | 78 | 最后,你可以设置一个代码所要输出的值的最大宽度。执行下面命令: 79 | 80 | :::vim 81 | :set statusline=%F 82 | 83 | `%F`会显示当前文件的*完整*路径。现在执行下面的命令改变最大宽度: 84 | 85 | :::vim 86 | :set statusline=%.20F 87 | 88 | 如果有必要路径会被删简,像下面这样: 89 | 90 | :::text 91 | 45 | 46 | 现在把下面的文字输入到缓冲区: 47 | 48 | :::python 49 | def count(i): 50 | i += 1 51 | print i 52 | 53 | return foo 54 | 55 | 把光标放到第二行的`i`上,然后按下`db`。会发生生么?Vim把整个函数体中直到`return`上面的内容都删除了,`return`就是上面的映射使用Vim的通用查找得到的结果。 56 | 57 | 当你想搞清楚怎么定义一个新的operator-pending movement的时候,你可以从下面几个步骤来思考: 58 | 59 | 1. 在光标所在的位置开始。 60 | 2. 进入可视模式(charwise)。 61 | 3. ... 把映射的按键放到这里 ... 62 | 4. 所有你想包含在movement中的文字都会被选中。 63 | 64 | 你所要做的工作就是在第三步中填上合适的按键。 65 | 66 | 改变开始位置 67 | ------------------ 68 | 69 | 你可能已经从上面所学的东西中意识到一个了问题。如果我们定义的movements都是从光标所在的位置开始的话,那么这就会限制我们做一些我们想使用movement来做的事情。 70 | 71 | 但是Vim并不会限制你去做你想做的事情,所以对于这个问题肯定有解决办法。执行下面的命令: 72 | 73 | :::vim 74 | :onoremap in( :normal! f(vi( 75 | 76 | 这个命令看起来有些复杂,不过我们还是先试试它能干什么。将下面的文字输入缓冲区: 77 | 78 | :::python 79 | print foo(bar) 80 | 81 | 把光标放到单词`print`上面,然后敲击`cin(`。Vim会删除括号内的内容然后进入插入模式,并且光标会停留在括号的中间。 82 | 83 | 你可以将这个映射理解为“在下一个括号内(inside next parentheses)”。它会对当前行光标所在位置的下一个括号内的文本执行operator。 84 | 85 | 我们再创建一个“在上一个括号内(inside last parentheses)”的movement进行对照。(在这里使用“前一个(previous)“可能更准确,但这会覆盖“段落(paragraph)”movement) 86 | 87 | 88 | :::vim 89 | :onoremap il( :normal! F)vi( 90 | 91 | 先试试确保这个命令可以工作。 92 | 93 | 那么这些映射是怎么工作的呢?首先,``比较特殊,可以先不用管(你只需要相信我这个东西可以让这个映射在任何情况下都能正常工作)。如果我们删除它的话,这个映射会变成这个样子: 94 | 95 | :::vim 96 | :normal! F)vi( 97 | 98 | `:normal!`会在后面的章节谈到,现在你只需要知道它可以在常用模式下模拟按下按键。例如,运行 99 | `:normal! dddd`会删除两行,就像按下`dddd`。映射后面的``是用来执行`:normal!`命令的。 100 | 101 | 那么现在我们可以认为这个映射的关键是运行下面这些按键组成的命令: 102 | 103 | :::vim 104 | F)vi( 105 | 106 | This is fairly simple: 107 | 这个命令很容易理解: 108 | 109 | * `F)`: 向后移动到最近的`)`字符。 110 | * `vi(`: 进入可视模式选择括号内的所有内容。 111 | 112 | 这个movement结束在在可视模式下选择中我们想操作的文本,然后Vim会对选中的文本执行操作,就像通常情况一样。 113 | 114 | 一般规则 115 | ------------- 116 | 117 | 下面两条规则可以让你可以很直观的以多种方式创建operator-pending映射: 118 | 119 | * 如果你的operator-pending映射以在可视模式下选中文本结束,Vim会操作这些文本。 120 | * 否则,Vim会操作从光标的原始位置到一个新位置之间的文本。 121 | 122 | 练习 123 | --------- 124 | 125 | 为"around next parentheses"和"around last 126 | parentheses"创建operator-pending映射 127 | 128 | 为打括号创建类似的in/around next/last的mappings。 129 | 130 | 阅读`:help omap-info`,看看你可不可以搞清楚``是干啥的。 131 | 132 | -------------------------------------------------------------------------------- /chapters/12.markdown: -------------------------------------------------------------------------------- 1 | 自动命令 2 | ============ 3 | 4 | 现在我们谈论一个跟映射一样重要的东西:自动命令。 5 | 6 | 自动命令可以让Vim自动执行某些指定的命令,这些指定的命令会在某些事件发生的时候执行。我们先看一个例子。 7 | 8 | 使用`:edit foo`打开一个新文件,然后立即使用`:quit`关闭。查看你的硬盘,你会发现这个文件并不存在。这是因为在你第一次保存这个文件之前,Vim*实际上*并没有真正创建它。 9 | 10 | 让我们对Vim做一些改变,使得Vim可以在你开始编辑文件的时候就创建它们。执行下面的命令: 11 | 12 | :::vim 13 | :autocmd BufNewFile * :write 14 | 15 | 这里面有很多需要进一步说明的,不过在此之前我建议你先感受下它是怎么工作的。执行`:edit foo`,使用`:quit`关闭,然后查看硬盘。这个时候文件会存在(当然文件内容为空)。 16 | 17 | 你只有关闭Vim才能删除这个自动命令。我们会在后面的章节说明如何避免这种情况。 18 | 19 | 自动命令结构 20 | --------------------- 21 | 22 | 让我们来深入分析下我们刚才创建的自动命令: 23 | 24 | :::text 25 | :autocmd BufNewFile * :write 26 | ^ ^ ^ 27 | | | | 28 | | | 要执行的命令 29 | | | 30 | | 用于事件过滤的“模式(pattern)” 31 | | 32 | 要监听的“事件” 33 | 34 | 这个命令的第一部分是我们想监听的事件的类型。Vim提供了*很多*可以监听的事件。这些事件包括: 35 | 36 | * 开始编辑一个当前并不存在的文件。 37 | * 读取一个文件,不管这个文件是否存在。 38 | * 改变一个缓冲区的`filetype`设置。 39 | * 在某段时间内不按下键盘上面的某个按键。 40 | * 进入插入模式。 41 | * 退出插入模式。 42 | 43 | 上面只举出了可用事件里面的很小一部分。还有很多其他的事件,你可以利用这些事件来做一些有趣的事情。 44 | 45 | 这个自动命令的下一部分是一个“模式”,这个模式可以进一步限定你要执行的命令的执行范围。新开一个Vim实例,执行下面的命令: 46 | 47 | :::vim 48 | :autocmd BufNewFile *.txt :write 49 | 50 | 这个跟之前的那个自动命令基本一样,不过这个自动命令只对后缀为`.txt`的文件有效,也就是说当你新建的文件为txt文件的时候,Vim会在文件创建的时候自动执行write命令将文件保存到硬盘上。 51 | 52 | 试试执行`:edit bar`,然后执行`:quit`,再执行`:edit bar.txt`,然后再执行`:quit`。你会发现Vim会自动创建`bar.txt`,但不会创建`bar`,因为它的后缀名不是txt,不跟模式匹配。 53 | 54 | 这个自动命令的最后一部分是事件发生时我们想执行的命令。这个部分很容易理解,跟我们执行其他命令一样,除了不能在这个命令中使用特殊的字符,例如``。我们会在本书后面的章节中谈论如何突破这个限制,现在你只需要遵守它就可以。 55 | 56 | 再来一个示例 57 | --------------- 58 | 59 | 我们再定义一个自动命令,这次使用一个不同的事件。执行下面的命令: 60 | 61 | :::vim 62 | :autocmd BufWritePre *.html :normal gg=G 63 | 64 | 这里用到了`normal`命令,我会在本书的后面的章节里面讲到它,这可能有点超前,不过我觉得这是一个很好的使用自动命令的示例,所以请大家先忍受一下。 65 | 66 | 创建一个名为`foo.html`的新文件。用Vim编辑它,并输入下面的文本,请保证输入的文本*完全一致*,包括空白符: 67 | 68 | :::html 69 | 70 | 71 |

Hello!

72 | 73 | 74 | 75 | 执行`:w`保存这个文件。看看会发生了什么?Vim似乎在文件保存之前重新进行了文本缩进处理。 76 | 77 | ok,请先相信我文本缩进处理是`:normal gg=G`干的,先别纠结于为什么`:normal gg=G`可以干这个。 78 | 79 | 我们*应该*把注意力放在自动命令上。这个自动命令里面用到的事件是`BufWritePre`,这个事件会在你保存*任何*字符到文件之前触发。 80 | 81 | 82 | 我们使用了`*.html`这个模式,这个模式会保证命令只会在编辑html文件的时候被执行。这就是自动命令强大的地方,因为它可以专门针对特定类型的文件来执行我们想要执行的命令。ok,让我们继续探索它吧。 83 | 84 | 多个事件 85 | --------------- 86 | 87 | 你可以创建一个绑定*多个*事件的自动命令,这些事件使用逗号分隔开。执行下面的命令: 88 | 89 | :::vim 90 | :autocmd BufWritePre,BufRead *.html :normal gg=G 91 | 92 | 这个跟上面的自动命令基本一样,不同的是它会让Vim不仅在写html文件的时候进行缩进处理,读html文件的时候也会进行缩进处理。如果你有些同事不喜欢把HTML文件格式搞得漂亮点,那么这个命令会很有用。 93 | 94 | 在Vim脚本编程中有一个不成文的规定,你应该同时使用`BufRead`和`BufNewFile`(译注:这里不是BufWritePre)这两个事件来运行命令,这样当你打开某个类型的文件,不论这个文件是否存在命令都会执行。执行下面的命令: 95 | 96 | :::vim 97 | :autocmd BufNewFile,BufRead *.html setlocal nowrap 98 | 99 | 上面的命令会使得无论你在什么时候编辑HTML文件自动换行都会被关闭。 100 | 101 | FileType事件 102 | --------------- 103 | 104 | 最有用的事件是`FileType`事件。这个事件会在Vim设置一个缓冲区的`filetype`的时候触发。 105 | 106 | 让我们针对不同文件类型设置一些有用的映射。运行命令: 107 | 108 | :::vim 109 | :autocmd FileType javascript nnoremap c I// 110 | :autocmd FileType python nnoremap c I# 111 | 112 | 打开一个Javascript文件(后缀为`.js`的文件),将光标移动到某一行,敲击`c`,光标所在的那一行会被注释掉。 113 | 114 | 现在打开一个Python文件(后缀为`.py`的文件),将光标移动到某一行,敲击`c`,同样的那一行会被注释掉,不同的是此时所用的是Python的注释字符! 115 | 116 | 在自动命令中包含我们上一章中学到的本地缓冲区映射,我们可以创建一些映射,这些映射会根据我们正在编辑的文件的类型来进行不同的处理。 117 | 118 | 这可以为我们在编码的时候减轻很多思考的负担。如果要添加一个注释,我们可能想到的是必须将光标移动到行首,然后添加一个注释字符,而使用上面的映射,我们只需要简单的将其理解为“注释掉这一行”。 119 | 120 | 练习 121 | --------- 122 | 123 | 浏览`:help autocmd-events`查看自动命令可以绑定的所有事件。你不需要现在就记住每一个事件。仅仅只需要了解下你可以使用这些事件做哪些事情。 124 | 125 | 创建一些`FileType`自动命令使用`setlocal`对你喜欢的文件类型做一些设置。你可以针对不同的文件类型设置`wrap`、`list`、 `spell`和`number`这些选项。 126 | 127 | 对一些你会经常处理的文件类型创建一些类似“注释掉这一行”的命令。 128 | 129 | 把所有这些自动命令写到你的`~/.vimrc`文件里面。记住使用前面章节中提到过的快速编辑和加载`~/.vimrc`文件的映射来做这个事情,这是必须的! 130 | 131 | 132 | -------------------------------------------------------------------------------- /chapters/31.markdown: -------------------------------------------------------------------------------- 1 | 基本的正则表达式 2 | ========================= 3 | 4 | Vim是一个文本编辑器,这意味着大量的Vimscript代码将专注于处理文本。 5 | Vim对正则表达式有着强大的支持,尽管一如既往地也有着一些坑。 6 | 7 | 把下面的文本打到缓冲区中: 8 | 9 | :::text 10 | max = 10 11 | 12 | print "Starting" 13 | 14 | for i in range(max): 15 | print "Counter:", i 16 | 17 | print "Done" 18 | 19 | 这个就是我们将用来测试Vimscript的正则支持的文本。它恰好是Python代码,但不要担心你看不懂Python。 20 | 它只是一个例子。 21 | 22 | 我会假定你懂得基本的正则表达式。如果你不懂, 23 | 你应该暂停阅读本书并开始阅读Zed Shaw的[Learn Regex the Hard Way][regex]。 24 | (译注:暂无中文版,也可选择别的书,或者just Google it) 25 | 在你看完后再继续。 26 | 27 | [regex]: http://regex.learncodethehardway.org/ 28 | 29 | 高亮 30 | ------------ 31 | 32 | 在开始之前,先花点时间讲讲搜索高亮,这样我们可以让匹配的内容更明显。 33 | 34 | :::vim 35 | :set hlsearch incsearch 36 | 37 | `hlsearch`让Vim高亮文件中所有匹配项,`incsearch`则令Vim在你正打着搜索内容时就高亮下一个匹配项 38 | 39 | 搜索 40 | --------- 41 | 42 | 移动你的光标到文件顶部并执行下面命令: 43 | 44 | :::vim 45 | /print 46 | 47 | 当你逐字母敲打时,Vim开始在第一行高亮它们。当你按下回车来进行搜索时,高亮*所有*的`print`, 48 | 同时移动你的光标到下一处匹配。 49 | 50 | 现在尝试执行下面的命令: 51 | 52 | :::vim 53 | :execute "normal! gg/print\" 54 | 55 | 这将移动到文件顶部并开始搜索`print`,带我们到第一处匹配。 56 | 用的是我们前一章看过的`:execute "normal! ..."`语法。 57 | 58 | 要到达文件中的第二处匹配,你仅需在命令的结尾加一点别的。执行这个命令: 59 | 60 | :::vim 61 | :execute "normal! gg/print\n" 62 | 63 | Vim将移动光标到缓冲区中的第二个`print`(同时高亮所有匹配)。 64 | 65 | 让我们尝试从反方向开始。执行这个命令: 66 | 67 | :::vim 68 | :execute "normal! G?print\" 69 | 70 | 这次我们用`G`移动到文件结尾并用`?`来反向搜索。 71 | 72 | 所有的搜索命令应该已经烂熟于心 —— 我们在让你习惯`:execute "normal! ..."`惯用法时已经反复练习过, 73 | 因为它让你在Vimscript代码中能够做日常在Vim里做的事。 74 | 75 | 魔力(Magic) 76 | ----- 77 | 78 | `/`和`?`命令能接受正则表达式,而不仅仅是普通字符。执行下面命令: 79 | 80 | :::vim 81 | :execute "normal! gg/for .+ in .+:\" 82 | 83 | Vim抱怨说找不到模式!我告诉过你Vim支持正则搜索,所以为何如此?试试下面命令: 84 | 85 | :::vim 86 | :execute "normal! gg/for .\\+ in .\\+:\" 87 | 88 | 这次Vim高亮"for"循环,如我们一开始所指望的。在继续阅读之前,花一分钟来想想为何如此。 89 | 记住`execute`接受一个字符串。 90 | 91 | 答案在此:我们需要这样写命令的原因有二: 92 | 93 | * 首先,`execute`接受一个字符串,在调用`normal!`命令时,双反斜杠将转换成单反斜杠。 94 | * Vim有四种不同的解析正则表达式的"模式"! 95 | 默认模式下需要在`+`前加上一个反斜杠来让它表示"一或多个之前的字符"而不是"一个字面意义上的加号"。 96 | 97 | 直接在Vim里执行搜索,你很容易就注意到它们的不同,输入下面的命令并按下回车: 98 | 99 | :::vim 100 | /print .\+ 101 | 102 | 现在你可以看到`\+`的魔力了。双反斜杠仅仅在把模式作为字符串传递给`execute`时才需要。 103 | 104 | 字面量字符串 105 | --------------- 106 | 107 | 正如我们在字符串那一章提到的,Vim允许你使用单引号来定义可以直接传递字符的字面量字符串。 108 | 比如,字符串`'a\nb'`有四个字符长。 109 | 110 | 我们可以使用字面量字符串来避免频繁敲打双重反斜杠吗? 111 | 先思考这个问题一两分钟,毕竟答案恐怕比你所认为的要更复杂一些。 112 | 113 | 试试执行下面的命令(注意这次的单引号和单反斜杠): 114 | 115 | :::vim 116 | :execute 'normal! gg/for .\+ in .\+:\' 117 | 118 | Vim带你到文件的顶部却不再移动到第一个匹配的地方。你猜对了吗? 119 | 120 | 命令之所以不能工作,是因为我们需要模式中的`\`被转义成回车,来启动搜索。 121 | 因为我们用的是字面量字符串,它并不等价于平常在Vim里键入`/for .\+ in .\+:\`, 122 | 显然这是无法工作的。 123 | 124 | 别怕,方法还是比困难多!不要忘了Vim允许字符串连接,所以可以将命令分割成容易理解的一小段。 125 | 执行下面的命令: 126 | 127 | :::vim 128 | :execute "normal! gg" . '/for .\+ in .\+:' . "\" 129 | 130 | 这种方法可以在传递给`execute`之前把三小段字符串连接起来, 131 | 而且我们可以为正则使用字面量字符串并为其他的使用一般的字符串。 132 | 133 | 更多的魔力(Very Magic) 134 | ---------- 135 | 136 | 你可能会好奇Vimscript的四种不同的正则解析模式和它们跟Python,Perl或Ruby中的正则表达式有何不同。 137 | 你可以阅读它们的文档,如果你乐意。不过如果你只想找到一种简单科学的解决办法,请继续读下去。 138 | 139 | 执行下面的命令: 140 | 141 | :::vim 142 | :execute "normal! gg" . '/\vfor .+ in .+:' . "\" 143 | 144 | 我们又一次把正则表达式放在单独的字面量字符串里,而这次我们用`\v`来引导模式。 145 | 这将告诉Vim使用它的"very magic"正则解析模式,而该模式就跟其他语言的非常相似。 146 | 147 | 如果你以`\v`开始你的所有正则表达式,你就不用再纠结Vimscript另外三种疯狂的正则模式了。 148 | 149 | 练习 150 | --------- 151 | 152 | 认真阅读`:help magic`。 153 | 154 | 阅读`:help pattern-overview`来看看Vim支持的正则类型。在看到character classes时停下来。 155 | 156 | 阅读`:help match`。尝试手动执行几次`:match Error /\v.../`。 157 | 158 | 在你的`~/.vimrc`文件中加入使用`match`来高亮多余的空白为错误的映射。建议使用`w`。 159 | 160 | 加入另一个映射来清除匹配项(比如`W`)。 161 | 162 | 加入一个normal模式下的会在进行搜索时自动插入`\v`的映射。 163 | 如果你卡在这个练习上,不要忘了Vim的映射是非常简单的,你只需要告诉它把映射键转换成哪些键。 164 | 165 | 在你的`~/.vimrc`文件中加入`hlsearch`和`incsearch`选项,随你所欲地设置它。 166 | 167 | 阅读`:help nohlsearch`。注意这是一个*命令*并且*不是*`hlsearch`的"off mode"。 168 | 169 | 在你的`~/.vimrc`文件中加入消除最后一次搜索的匹配项的高亮的映射。 170 | -------------------------------------------------------------------------------- /chapters/38.markdown: -------------------------------------------------------------------------------- 1 | 切换 2 | ======== 3 | 4 | 在开头前几章我们曾讲过怎么在Vim里设置选项。 5 | 对于布尔选项,我们可以使用`set someoption!`来"切换"选项。 6 | 如果我们能给这个命令创建一个映射,那就再好不过了。 7 | 8 | 执行下面的命令: 9 | 10 | :::vim 11 | :nnoremap N :setlocal number! 12 | 13 | 在normal模式中按下`N`看看。Vim将会在开启和关闭行号显示之间切换。 14 | 像这样的"切换"映射是十分方便的,因此我们就不需要两个独立的键来开/关。 15 | 16 | 不幸的是,这只对布尔选项起作用。如果我们想要切换一个非布尔选项,还需要做更多的工作。 17 | 18 | 切换选项 19 | ---------------- 20 | 21 | 从创建一个可以切换选项的函数,以及调用该函数的映射开始吧。 22 | 把下面的代码加入到你的`~/.vimrc`(或一个`~/.vim/plugin/`中的独立文件,如果你想要的话): 23 | 24 | :::vim 25 | nnoremap f :call FoldColumnToggle() 26 | 27 | function! FoldColumnToggle() 28 | echom &foldcolumn 29 | endfunction 30 | 31 | 保存并source文件,然后按下`f`试试看。Vim显示当前`foldcolumn`选项的值。 32 | 如果你不熟悉这个选项,阅读`:help foldcolumn`再继续。 33 | 34 | 让我们添加真正的切换功能。修改代码成这样: 35 | 36 | :::vim 37 | nnoremap f :call FoldColumnToggle() 38 | 39 | function! FoldColumnToggle() 40 | if &foldcolumn 41 | setlocal foldcolumn=0 42 | else 43 | setlocal foldcolumn=4 44 | endif 45 | endfunction 46 | 47 | 保存并source文件,然后试试看。每次你按下它Vim将显示或隐藏折叠状态条(fold column)。 48 | 49 | `if`语句判断`&foldcolumn`是否为真(记住Vim把0看作假而其他数字为真)。 50 | 如果是,把它设成0(隐藏它)。否则就设置它为4。就是这么简单。 51 | 52 | 你可以使用一个简单的函数像这样来切换任何以`0`代表关,以其他数字代表开的选项。 53 | 54 | 切换其他东西 55 | --------------------- 56 | 57 | 我们的梦想不应止于切换选项。还有一个我们想切换的东西是quickfix窗口。 58 | 依然以之前的骨架代码作为起点。加入下面的代码到你的文件: 59 | 60 | :::vim 61 | nnoremap q :call QuickfixToggle() 62 | 63 | function! QuickfixToggle() 64 | return 65 | endfunction 66 | 67 | 这个映射暂时什么都不干。让我们把它转变成其他稍微有点用的东西(不过还没有彻底完成)。 68 | 把代码改成这样: 69 | 70 | :::vim 71 | nnoremap q :call QuickfixToggle() 72 | 73 | function! QuickfixToggle() 74 | copen 75 | endfunction 76 | 77 | 保存并source文件。如果现在你试一下这个映射,你就会看到一个空荡荡的quickfix窗口。 78 | 79 | 为了达到实现切换功能的目的,我们将选择一个既快捷又肮脏的手段:全局变量。 80 | 把代码改成这样: 81 | 82 | :::vim 83 | nnoremap q :call QuickfixToggle() 84 | 85 | function! QuickfixToggle() 86 | if g:quickfix_is_open 87 | cclose 88 | let g:quickfix_is_open = 0 89 | else 90 | copen 91 | let g:quickfix_is_open = 1 92 | endif 93 | endfunction 94 | 95 | 我们干的事情十分简单 —— 每次调用函数时,我们用一个全局变量来储存quickfix窗口的开关状态。 96 | 97 | 保存并source文件,接着执行映射试试看。Vim将抱怨变量尚未定义!那么我们先把变量初始化吧。 98 | 99 | :::vim 100 | nnoremap q :call QuickfixToggle() 101 | 102 | let g:quickfix_is_open = 0 103 | 104 | function! QuickfixToggle() 105 | if g:quickfix_is_open 106 | cclose 107 | let g:quickfix_is_open = 0 108 | else 109 | copen 110 | let g:quickfix_is_open = 1 111 | endif 112 | endfunction 113 | 114 | 保存并source文件,接着试一下映射。成功了! 115 | 116 | 改进 117 | ------------ 118 | 119 | 我们的切换函数可以工作,但还留有一些问题。 120 | 121 | 第一个问题是,假设用户用`:copen`或`:cclose`手动开关窗口,我们的全局变量将不会刷新。 122 | 实际上这不会是个大问题,因为大多数情况下用户会用这个映射开关窗口,万一没有打开,他们也会再按一次。 123 | 124 | 这又是关于写Vimscript代码的重要经验:如果你试图处理每一个边际条件,你将陷在里面,而且不会有任何进展。 125 | 126 | 在大多数情况下,先推出可工作(而且即使不能工作也不会造成破坏)的代码然后回过头改善, 127 | 要比耗费许多小时苛求完美好得多。除外你正在开发一个很可能有很多人用到的插件。 128 | 在这种情况下它才值得耗费时日来达到无懈可击的程度,让用户满意并减少bug报告。 129 | 130 | 重新加载窗口/缓冲区 131 | ------------------------- 132 | 133 | 我们的函数的另外一个问题是,当用户已经打开了quickfix窗口,并执行这个映射时, 134 | Vim关闭了窗口,接着把他们弹到上一个分割中,而不是送他们回之前的地方。 135 | 如果你仅仅想快速查看一下quickfix窗口然后继续工作,发生这种事是让人恼怒的。 136 | 137 | 为了解决这个问题,我们将引入一种写Vim插件时非常有用的惯用法。把你的代码改成这样: 138 | 139 | :::vim 140 | nnoremap q :call QuickfixToggle() 141 | 142 | let g:quickfix_is_open = 0 143 | 144 | function! QuickfixToggle() 145 | if g:quickfix_is_open 146 | cclose 147 | let g:quickfix_is_open = 0 148 | execute g:quickfix_return_to_window . "wincmd w" 149 | else 150 | let g:quickfix_return_to_window = winnr() 151 | copen 152 | let g:quickfix_is_open = 1 153 | endif 154 | endfunction 155 | 156 | 我们在映射中加入了新的两行。其中一行(在`else`分支)设置了另一个全局变量,来保存执行`:copen`时的当前窗口序号。 157 | 158 | 另一行(在`if`分支)执行以那个序号作前缀的`wincmd w`,来告诉Vim跳转到对应窗口。 159 | 160 | 我们的解决方法又一次不是无懈可击的,用户可能在两次执行映射之间打开或关闭新的分割。 161 | 即使这样,它还是适合于大多数场合,所以目前这已经够好的了。 162 | 163 | 在大多数程序中,这种手工保存全局状态的伎俩会遭到谴责,但对于一个非常短小的Vimscript函数而言, 164 | 它既快捷又肮脏,却能不辱使命,完成重任。 165 | 166 | 练习 167 | --------- 168 | 169 | 阅读`:help foldcolumn`. 170 | 171 | 阅读`:help winnr()` 172 | 173 | 阅读`:help ctrl-w_w`. 174 | 175 | 阅读`:help wincmd`. 176 | 177 | 在需要的地方加上`s:`和``来把函数限定在独自的命名空间中。 178 | -------------------------------------------------------------------------------- /chapters/16.markdown: -------------------------------------------------------------------------------- 1 | 更多Operator-Pending映射 2 | ============================== 3 | 4 | Operators和movements所包含的理念是Vim中的一个非常重要的概念,也是Vim之所以这么高效的最大原因所在。在这一章我们会在这一块做更多的实践,这会让Vim变得更强大。 5 | 6 | 假设你现在在往Markdown中写入一些文字。如果你没有用过Markdown,不要紧,对于我们现在要做的事情而言,它很简单。把下面的文字输入到一个文件中: 7 | 8 | :::markdown 9 | Topic One 10 | ========= 11 | 12 | This is some text about topic one. 13 | 14 | It has multiple paragraphs. 15 | 16 | Topic Two 17 | ========= 18 | 19 | This is some text about topic two. It has only one paragraph. 20 | 21 | 使用`=`作为“下划线”的行会被Markdown当作标题。我们现在创建一些映射,这些映射可以让我们通过movements定位到标题。运行下面的命令: 22 | 23 | :::vim 24 | :onoremap ih :execute "normal! ?^==\\+$\r:nohlsearch\rkvg_" 25 | 26 | 这个映射有些复杂。现在把你的光标放到文本中的某个位置(不要放到标题上),然后敲击`cih`。Vim会删除光标所在章节的标题,然后保持在插入模式,这可以称为"修改所在的标题(change inside heading)"。 27 | 28 | 这里使用了一些我们之前从来没有见过的东西,所以我们有必要单独分析下每一部分的含义。这个映射的第一部分,`:onoremap ih`是映射命令,这个我们很熟悉了,无需多言。``上一章讲过,我们现在也不深究。 29 | 30 | 接着看看剩下的部分: 31 | 32 | :::vim 33 | :execute "normal! ?^==\\+$\r:nohlsearch\rkvg_" 34 | 35 | Normal 36 | ------ 37 | 38 | `:normal`命令的后面会跟着一串字符,无论这些字符表示什么含义,`:normal`命令都会执行它们,就像是在常用模式下敲击这些字符一样。我们会在后面的章节中谈论关于`:normal`的更多细节,由于这个它已经出现多次,所以我觉得有必要现在做一个简单的说明,算是浅尝辄止吧。执行下面的命令: 39 | 40 | :::vim 41 | :normal gg 42 | 43 | Vim会将光标跳转到文件的顶部。现在执行下面的命令: 44 | 45 | :::vim 46 | :normal >> 47 | 48 | Vim将缩进当前行。 49 | 50 | 那`normal`后面的`!`是干啥的呢?先别管,以后再说。 51 | 52 | Execute 53 | ------- 54 | 55 | `execute`命令后面会跟着一个Vim脚本字符串(以后会深究它的细节),然后把这个字符串当作一个命令执行。执行下面的命令: 56 | 57 | :::vim 58 | :execute "write" 59 | 60 | Vim会写文件,就像你已经输入了`:write`一样。现在执行下面的命令: 61 | 62 | :::vim 63 | :execute "normal! gg" 64 | 65 | Vim会执行`:normal! gg`,这个会将光标跳转到文件的顶部,跟之前一样。现在问题来了,我们为什么要搞得这么蛋疼,又是`execute`,又是`normal!`,直接执行`normal!`不就可以搞定么? 66 | 67 | 看看下面的命令,猜猜它会干啥: 68 | 69 | :::vim 70 | :normal! gg/a 71 | 72 | 这个命令似乎会做下面的一些事情: 73 | 74 | * 将光标跳转到文件的顶部。 75 | * 准备搜索。 76 | * 把“a”当作目标字符串进行搜索。 77 | * 按下return键执行搜索。 78 | 79 | 你自己执行一下,Vim会将光标跳转到了文件顶部,然后。。没有然后了! 80 | 81 | 之所以会这样是由于`normal!`的一个问题,这问题是`normal!`不能识别“特殊字符”,例如这里的``。这个问题有很多办法可以搞定,最简单的就是使用`execute`,另外使用`execute`也会让脚本更易读。 82 | 83 | 当`execute`碰到任何你想让它执行的字符串的时候。它会先替换这个字符串中的所有特殊字符。在这个示例中,`\r`是一个转义字符,它表示的是"回车符(carriage return)"。两个反斜线也是一个转义字符,它会将一个反斜线当作一般字符放到字符串中。 84 | 85 | 如果我们按照上面的分析替换这个映射中的特殊字符,然后就可以搞清楚这个映射会怎么执行: 86 | 87 | :::text 88 | :normal! ?^==\+$:nohlsearchkvg_ 89 | ^^^^ ^^^^ 90 | || || 91 | 这里的实际上是一个回车符,而不是由4个字符——“左尖括号”,“c“,”r“和“右尖括号”组成的字符串。 92 | 93 | 所以现在`normal!`会执行这些字符,如同我们是在常用模式下敲击了它们一样。我们以回车符对这些字符进行拆分,然后看看它们是怎么执行的: 94 | 95 | :::vim 96 | ?^==\+$ 97 | :nohlsearch 98 | kvg_ 99 | 100 | 第一部分`?^==\+$`会向后搜索任何由两个或多个等号组成的行,这些行不会包含除等号外的任何其他字符。这个命令执行后会让光标停留在符合搜索条件的行的行首。 101 | 102 | 之所以使用向后搜索,是因为当你想“修改所在的标题(change inside heading)”的时候,而当前光标是位于某一节的文本上,那么你最可能想做的是修改*这*一节的标题,而不是下一节的标题。 103 | 104 | 第二部分是`:nohlsearch`命令。这个命令只是简单的清除之前的搜索结果的高亮显示,防止这些高亮显示分散我们的注意。 105 | 106 | 最后一部分是三个常用模式下的命令的序列: 107 | 108 | * `k`:向上移动一行。第一部分已经将光标定位到了等号符号组成的行的第一个字符,所以执行这个命令后光标就会被定位到标题的第一个字符上。 109 | * `v`:进入可视模式(characterwise)。 110 | * `g_`:移动到当前行的最后一个非空字符上。这里没有使用`$`,是因为`$`会选中换行符号,这不是我们所想要的。 111 | 112 | 结果 113 | ------- 114 | 115 | 这个映射做了很多工作,所以看起来有些复杂,不过我们已经搞清楚了这个映射中的每个部分。现在来概括一下: 116 | 117 | * 我们为“所在章节的标题内(inside this section's heading)”创建了一个operator-pending的映射。 118 | * 我们使用了`execute`和`normal!`这两个命令来执行我们用于选择标题的常用命令,这让我们可以在这些命令中使用特殊字符。 119 | * 我们的映射会搜索由等号组成的行从而定位到一个标题,然后在常用模式下选中标题的文本。 120 | * Vim进行剩下的处理标题的工作。 121 | 122 | 再来看一个映射。执行下面的命令: 123 | 124 | :::vim 125 | :onoremap ah :execute "normal! ?^==\\+\r:nohlsearch\rg_vk0" 126 | 127 | 把光标放到某一节的文字上,然后敲击`cah`试试看。这一次Vim不仅会删除这一节的标题,而且还会删除跟这个标题相连的等号组成的行。你可以把这个movement当作是“*环绕*这一节的标题(*around* this section's heading)“。 128 | 129 | 这个映射有什么不同呢?让我们对照之前的映射看一下: 130 | 131 | :::vim 132 | :onoremap ih :execute "normal! ?^==\\+$\r:nohlsearch\rkvg_" 133 | :onoremap ah :execute "normal! ?^==\\+$\r:nohlsearch\rg_vk0" 134 | 135 | 唯一的不同是映射的后面用于选择文本的部分: 136 | 137 | :::text 138 | inside heading: kvg_ 139 | around heading: g_vk0 140 | 141 | 其他的部分都是一模一样的,所以我们现在从将光标定位到等号组成的行的第一个字符的那个部分开始进行讲解: 142 | 143 | * `g_`:移动到当前行(译注:等号组成的行)的最后一个非空字符。 144 | * `v`:进入可视模式(characterwise)。 145 | * `k`:向上移动一行。这会将光标移动到包含标题文字的行上。 146 | * `0`:移动到这一行(译注:标题行)的第一个字符。 147 | 148 | 这一系列命令的执行结果就是在可视模式下同时选中标题的文字和等号组成的行,然后Vim会在这两行上执行相应的操作。 149 | 150 | 练习 151 | --------- 152 | 153 | Markdown也可以用`-`字符来限定标题。调整上面的正则表达式使得这些映射可以工作在不同类型的标题上。你可能想查看`:help pattern-overview`。记住正则表达是在一个字符串中,所以反斜线需要进行转义。 154 | 155 | 添加两个创建这些映射的自动命令到你的`~/.vimrc`文件中。确保只对合适的缓冲区使用这些映射,并且确保使用自动命令组来防止每次加载`~/.vimrc`的时候创建这些自动命令的副本。 156 | 157 | 阅读 `:help normal`。 158 | 159 | 阅读 `:help execute`。 160 | 161 | 阅读 `:help expr-quote`了解你可以在字符串中使用的转义序列。 162 | 163 | 创建一个“在下一个邮件地址内(inside next email address)”的operator-pending映射,然后你就可以使用“修改在下一个邮件地址内(change inside next email address)”。将`in@`作为映射的按键是个不错的选择。你可能还需要将这个按键映射为`/...some regex...`。 164 | 165 | -------------------------------------------------------------------------------- /chapters/39.markdown: -------------------------------------------------------------------------------- 1 | 函数式编程 2 | ====================== 3 | 4 | 现在让我们小憩一下,聊一聊一种你可能听过的编程风格:[函数式编程][]。 5 | 6 | 如果你用过Python,Ruby或Javascript,*甚或*Lisp,Scheme,Clojure或Haskell, 7 | 你应该会觉得把函数作为变量类型,用不可变的状态作为数据结构是平常的事。 8 | 如果你没用过,你可以放心地跳过这一章了,但我还是鼓励你找机会去试试并拓宽自己的视野。 9 | 10 | Vimscript具有使用函数式风格进行编程的潜力,不过会有点吃力。 11 | 我们可以创建一些辅助函数来让这个过程少些痛苦。 12 | 13 | 继续前进并创建`functional.vim`文件,这样你就不用反复地重新击打每一行代码。 14 | 这个文件将会成为这一章的草稿本。 15 | 16 | [函数式编程]: https://secure.wikimedia.org/wikipedia/en/wiki/Functional_programming 17 | 18 | 不可变的数据结构 19 | ------------------------- 20 | 21 | 不幸的是,Vim没有类似于Clojure内置的vector和map那样的不可变集合, 22 | 不过通过一些辅助函数,我们可以在一定程度上模拟出来。 23 | 24 | 在你的文件加上下面的函数: 25 | 26 | :::vim 27 | function! Sorted(l) 28 | let new_list = deepcopy(a:l) 29 | call sort(new_list) 30 | return new_list 31 | endfunction 32 | 33 | 保存并source文件,然后执行`:echo Sorted([3,2,4,1])`来试试看。 34 | Vim输出`[1,2,3,4]`。 35 | 36 | 这跟调用内置的`sort()`函数有什么区别呢?关键在于第一行:`let new_list = deepcopy(a:l)`。 37 | Vim的`sort()`*就地*重排列表,所以我们先创建一个列表的副本,并排序*副本*, 38 | 这样原本的列表不会被改变。 39 | 40 | 这样就避免了副作用,并帮助我们写出更容易推断和测试的代码。让我们加入更多同样风格的辅助函数: 41 | 42 | :::vim 43 | function! Reversed(l) 44 | let new_list = deepcopy(a:l) 45 | call reverse(new_list) 46 | return new_list 47 | endfunction 48 | 49 | function! Append(l, val) 50 | let new_list = deepcopy(a:l) 51 | call add(new_list, a:val) 52 | return new_list 53 | endfunction 54 | 55 | function! Assoc(l, i, val) 56 | let new_list = deepcopy(a:l) 57 | let new_list[a:i] = a:val 58 | return new_list 59 | endfunction 60 | 61 | function! Pop(l, i) 62 | let new_list = deepcopy(a:l) 63 | call remove(new_list, a:i) 64 | return new_list 65 | endfunction 66 | 67 | 除了中间的一行和它们接受的参数,每一个函数都是一样的。保存并source文件,在一些列表上试试它们。 68 | 69 | `Reversed()`接受一个列表并返回一个新的倒置了元素的列表。 70 | 71 | `Append()`返回一个在原列表的基础上增加了给定值的新列表。 72 | 73 | `Assoc()`("associate"的缩写)返回一个给定索引上的元素被替换成新值的新列表。 74 | 75 | `Pop()`返回一个给定索引上的元素被移除的新列表。 76 | 77 | 作为变量的函数 78 | ---------------------- 79 | 80 | Vimscript支持使用变量储存函数,但是相关的语法有点愚钝。执行下面的命令: 81 | 82 | :::vim 83 | :let Myfunc = function("Append") 84 | :echo Myfunc([1, 2], 3) 85 | 86 | Vim意料之中地显示`[1, 2, 3]`。注意我们使用的变量以大写字母开头。 87 | 如果一个Vimscript变量要引用一个函数,它就要以大写字母开头。 88 | 89 | 就像其他种类的变量,函数也可以储存在列表里。执行下面命令: 90 | 91 | :::vim 92 | :let funcs = [function("Append"), function("Pop")] 93 | :echo funcs[1](['a', 'b', 'c'], 1) 94 | 95 | Vim显示`['a', 'c']`。`funcs`变量*不*需要以大写字母开头,因为它储存的是列表,而不是函数。 96 | 列表的内容不会造成任何影响。 97 | 98 | 高阶函数 99 | ---------------------- 100 | 101 | 让我们创建一些用途广泛的高阶函数。如果你需要解释,高阶函数就是接受*别的*函数并使用它们的函数。 102 | 103 | 我们将从`map`函数开始。在你的文件中添加这个: 104 | 105 | :::vim 106 | function! Mapped(fn, l) 107 | let new_list = deepcopy(a:l) 108 | call map(new_list, string(a:fn) . '(v:val)') 109 | return new_list 110 | endfunction 111 | 112 | 保存并source文件,执行下面命令试试看: 113 | 114 | :::vim 115 | :let mylist = [[1, 2], [3, 4]] 116 | :echo Mapped(function("Reversed"), mylist) 117 | 118 | Vim显示`[[2, 1], [4, 3]]`,正好是对列表中的每一个元素应用了`Reversed()`的结果。 119 | 120 | `Mapped()`是如何起作用的?我们又一次用`deepcopy()`创建新的列表,修修改改,返回修改后的副本 121 | —— 没什么是新的。有门道的是中间的部分。 122 | 123 | `Mapped()`接受两个参数:一个funcref("储存一个函数的变量"在Vim里的说法)和一个列表。 124 | 我们使用内置的`map()`函数实现真正的工作。现在就阅读`:help map()`来看它怎么工作的。 125 | 126 | 现在我们将创建一些通用的高阶函数。把下面的代码加入到你的文件: 127 | 128 | :::vim 129 | function! Filtered(fn, l) 130 | let new_list = deepcopy(a:l) 131 | call filter(new_list, string(a:fn) . '(v:val)') 132 | return new_list 133 | endfunction 134 | 135 | 用下面的命令尝试`Filtered()`: 136 | 137 | :::vim 138 | :let mylist = [[1, 2], [], ['foo'], []] 139 | :echo Filtered(function('len'), mylist) 140 | 141 | Vim显示`[[1, 2], ['foo']]`。 142 | 143 | `Filtered()`接受一个谓词函数和一个列表。它返回一个列表的副本, 144 | 而这个列表只包括将自身作为谓词函数的输入参数并返回真值的元素。 145 | 这里我们使用了内置的`len()`,让它过滤掉所有长度为0的元素。 146 | 147 | 最后我们创建了`Filtered()`的好基友(counterpart): 148 | 149 | :::vim 150 | function! Removed(fn, l) 151 | let new_list = deepcopy(a:l) 152 | call filter(new_list, '!' . string(a:fn) . '(v:val)') 153 | return new_list 154 | endfunction 155 | 156 | 像使用`Filtered()`一样试一下: 157 | 158 | :::vim 159 | :let mylist = [[1, 2], [], ['foo'], []] 160 | :echo Removed(function('len'), mylist) 161 | 162 | Vim显示`[[], []]`。`Removed()`就像`Filtered()`,不过它只保留谓词函数返回*非*真值的元素。 163 | 164 | 代码中的唯一不同在于调用命令前面的`'!' . `,它把谓词函数的结果取反。 165 | 166 | 效率 167 | ----------- 168 | 169 | 考虑到Vim不得不持续地创建新的副本并垃圾回收旧的对象,你可能会认为不停地制造副本是种浪费。 170 | 171 | 是的,你是对的!Vim的列表不像Clojure的vector那样支持结构共享(structural sharing), 172 | 所以这里所有的复制操作是昂贵的。 173 | 174 | 有时这的确是个问题。如果你需要使用庞大的列表,程序就会因此变慢。 175 | 在现实世界,你可能会吃惊地发现你几乎不会注意到其中的差别。 176 | 177 | 想想看吧:当我正写下本章时,Vim占用了80M内存(而且我可是装了*一堆*插件)。 178 | 我的笔记本总共有*8G*内存。有一些列表的副本被创建出来,这会造成可被察觉的不同吗? 179 | 当然这取决于列表的大小,但在大多数情况下答案将会是"No"。 180 | 181 | 作为比较,我的Firefox打开了五个tab,现在正饕餮着*1.22G*内存。 182 | 183 | 你将需要自己判断,什么时候这种编程风格会导致不可接受的低效率。 184 | 185 | 练习 186 | --------- 187 | 188 | 阅读`:help sort()`。 189 | 190 | 阅读`:help reverse()`。 191 | 192 | 阅读`:help copy()`。 193 | 194 | 阅读`:help deepcopy()`。 195 | 196 | 阅读`:help map()`,如果你未曾读过。 197 | 198 | 阅读`:help function()`。 199 | 200 | 修改`Assoc()`, `Pop()`, `Mapped()`, `Filtered()`和`Removed()`来支持字典类型。 201 | 你可能需要阅读`:help type()`来帮助自己。 202 | 203 | 实现`Reduced()`。 204 | 205 | 倒给自己一杯最喜欢的饮料。这一章真激烈(intense)! 206 | -------------------------------------------------------------------------------- /chapters/32.markdown: -------------------------------------------------------------------------------- 1 | 实例研究:Grep 运算符(Operator),第一部分 2 | =================================== 3 | 4 | 在本章和下一章中,我们将使用Vimscript来实现一个相当复杂的程序。我们将探讨一些闻所未闻的东西, 5 | 也将在实战中把之前学过的东西联系起来。 6 | 7 | 在本实例研究中,遇到不熟悉的内容,你得用`:help`弄懂它。如果你只是走马观花,就将所获无多。 8 | 9 | Grep 10 | ---- 11 | 12 | 如果你未曾用过`:grep`,现在你应该花费一分钟读读`:help :grep`和`:help :make`。 13 | 如果之前没用过quickfix window,阅读`:help quickfix-window`。 14 | 15 | 简明扼要地说:`:grep ...`将用你给的参数来运行一个外部的grep程序,解析结果,填充quickfix列表, 16 | 这样你就能在Vim里面跳转到对应结果。 17 | 18 | 我们将会添加一个"grep运算符"到任意Vim的内置(或自定义!)的动作中,来选择想要搜索的文本, 19 | 让`:grep`更容易使用。 20 | 21 | 用法 22 | ----- 23 | 24 | 在写下每一个有意义的Vimscript程序的第一步,你需要思索一个问题:“它会被用户怎么使用呢?”。 25 | 尝试构思出一种优雅,简易,符合直觉的调用方法。 26 | 27 | 这次我会替你把这活干了: 28 | 29 | * 我们将创造一个"grep运算符"并绑定到`g`。 30 | * 它将表现得同其他任意Vim运算符一样,还可以加入到组合键(比如`w`和`i{`)中。 31 | * 它将立刻开始搜索并打开quickfix窗口展示结果。 32 | * 它将*不会*跳到第一个结果,因为当第一个结果不是你想要的时候,这样做会困扰你。 33 | 34 | 一些你将怎么使用它的用例: 35 | 36 | * `giw`: Grep光标下的词(word)。 37 | * `giW`: Grep光标下的词的大写形式(WORD)。 38 | * `gi'`: Grep当前所在的单引号括住的词。 39 | * `viweg`: 可视状态下选中一个词并拓展选择范围到下一词,然后Grep。 40 | 41 | 有很多,*很多*其他的方法可以用它。看上去它好像需要写很多,很多代码, 42 | 但事实上我们只需要实现"运算符"功能然后Vim就会完成剩下的工作。 43 | 44 | 一个原型 45 | -------------------- 46 | 47 | 在埋头写下巨量(trickey bits)的Vimscript之前,有一个也许会帮上忙的方法是简化你的目标并实现*它*, 48 | 来推测你最终解决方案可能的"外形"。 49 | 50 | 让我们简化我们的目标为"创造一个映射来搜索光标下的词"。这有用而且应该更简单,所以我们能更快得到可运行的成果。 51 | 目前我们将映射它到`g`。 52 | 53 | 我们从一个映射骨架开始并逐渐填补它。执行这个命令: 54 | 55 | :::vim 56 | :nnoremap g :grep -R something . 57 | 58 | 如果你阅读过`:help grep`,你就能轻易理解这个命令。我们之前也看过许多映射,这里没有什么是新的。 59 | 60 | 显然我们还没做什么,所以让我们一步步打磨这个映射直到它符合我们的要求。 61 | 62 | 搜索部分 63 | --------------- 64 | 65 | 首先我们需要搜索光标下的词,而不是`something`。执行下面的命令: 66 | 67 | :::vim 68 | :nnoremap g :grep -R . 69 | 70 | 现在试一下。``是一个Vim的command-line模式的特殊变量, 71 | Vim会在执行命令之前把它替换为"光标下面的那个词"。 72 | 73 | 你可以使用``来得到大写形式(WORD)。执行这个命令: 74 | 75 | :::vim 76 | :nnoremap g :grep -R . 77 | 78 | 现在试试把光标放在诸如`foo-bar`的词上面。Vim将grep`foo-bar`而不是其中的一部分。 79 | 80 | 我们的搜索部分还有一个问题:如果这里面有什么特殊的shell字符,Vim会毫不犹豫地传递给外部的grep命令。 81 | 这样会导致程序崩溃(或更糟:铸成某些大错)。 82 | 83 | 让我们看看如何使它挂掉。输入`foo;ls`并把光标放上去执行映射。grep命令失败了, 84 | 而Vim将执行`ls`命令!这肯定糟透了,如果词里包括比`ls`更危险的命令呢? 85 | 86 | 为了解决这个问题,我们将调用参数用引号括起来。执行这个命令: 87 | 88 | :::vim 89 | :nnoremap g :grep -R '' . 90 | 91 | 大多数shell把单引号括起来的内容当作(大体上)字面量,所以我们的映射现在更加健壮了。 92 | 93 | 转义Shell命令参数 94 | -------------------------------- 95 | 96 | 搜索部分还有一个问题。在`that's`上尝试这个映射。它不会工作,因为词里的单引号与grep命令的单引号发生了冲突! 97 | 98 | 为了解决问题,我们可以使用Vim的`shellescape`函数。 99 | 阅读`:help escape()`和`:help shellescape()`来看它是怎样工作的(真的很简单)。 100 | 101 | 因为`shellescape()`要求Vim字符串,我们需要用`execute`动态创建命令。 102 | 首先执行下面命令来转换`:grep`映射到`:execute "..."`形式: 103 | 104 | :::vim 105 | :nnoremap g :execute "grep -R '' ." 106 | 107 | 试一下并确信它可以工作。如果不行,找出拼写错误并改正。 108 | 然后执行下面的使用了`shellescape`的命令。 109 | 110 | :::vim 111 | :nnoremap g :execute "grep -R " . shellescape("") . " ." 112 | 113 | 在一般的词比如`foo`上执行这个命令试试。它可以工作。再到一个带单引号的词,比如`that's`,上试试看。 114 | 它还是不行!为什么会这样? 115 | 116 | 问题在于Vim在拓展命令行中的特殊变量,比如``,的*之前*,就已经执行了`shellescape()`。 117 | 所以Vim shell-escaped了字面量字符串`""`(什么都不做,除了给它添上一对单引号)并连接到我们的`grep`命令上。 118 | 119 | 通过执行下面的命令,你可以亲眼目睹这一切。 120 | 121 | :::vim 122 | :echom shellescape("") 123 | 124 | Vim将输出`''`。注意引号也是输出字符串的一部分。Vim把它作为shell命令参数保护了起来。 125 | 126 | 为解决这个问题,我们将使用`expand()`函数来强制拓展``为对应字符串, 127 | 抢在它被传递给`shellescape`*之前*。 128 | 129 | 让我们单独看看这一部分是怎么工作的。把你的光标移到带单引号的词(比如`that's`)上去, 130 | 并执行下面命令: 131 | 132 | :::vim 133 | :echom expand("") 134 | 135 | Vim输出`that's`,因为`expand("")`以Vim字符串的形式返回当前光标下的词。 136 | 是时候加入`shellescape`的部分了: 137 | 138 | :::vim 139 | :echom shellescape(expand("")) 140 | 141 | 这次Vim输出`'that'\''s'`。 142 | 如果觉得这看上去真可笑,你大概没有感受过看透了各种shell转义的疯狂形式后的淡定吧。 143 | 目前,不用为此而纠结。就相信Vim接受了`expand`的输出并正确地转义了它。 144 | 145 | 目前我们已经得到了光标下的词的彻底转义版本。是时候连接它到我们的映射了! 146 | 执行下面的命令: 147 | 148 | :::vim 149 | :nnoremap g :exe "grep -R " . shellescape(expand("")) . " ." 150 | 151 | 试一下。这个映射不再有问题,即使我们用它搜索带古怪符号的词。 152 | 153 | "从简单的Vimscript开始并一点点转变它直到达成你的目标"这样的工作方式将会被你一再取用。 154 | 155 | 整理整理 156 | ------- 157 | 158 | 在完成映射之前,还要处理一些小问题。首先,我们说过我们不想自动跳到第一个结果, 159 | 所以要用`grep!`替换掉`grep`。执行下面的命令: 160 | 161 | 162 | :::vim 163 | :nnoremap g :execute "grep! -R " . shellescape(expand("")) . " ." 164 | 165 | 再一次试试,发现什么都没发生。Vim用结果填充了quickfix窗口,我们却无法打开。 166 | 执行下面的命令: 167 | 168 | :::vim 169 | :nnoremap g :execute "grep! -R " . shellescape(expand("")) . " .":copen 170 | 171 | 现在试试这个映射,你将看到Vim自动打开了包含搜索结果的quickfix窗口。 172 | 我们所做的仅仅是在映射的结尾续上`:copen`。 173 | 174 | 最后一点,在搜索的时候,我们要移除Vim所有的grep输出。执行下面的命令: 175 | 176 | :::vim 177 | :nnoremap g :silent execute "grep! -R " . shellescape(expand("")) . " .":copen 178 | 179 | 我们完成了,试一试并犒劳一下自己吧!`silent`命令仅仅是在运行一个命令的同时隐藏它的正常输出。 180 | 181 | 练习 182 | --------- 183 | 184 | 把我们刚刚做出来的映射加入到你的`~/.vimrc`文件。 185 | 186 | 如果你未曾读过`:help :grep`,去读它。 187 | 188 | 阅读`:help cword`。 189 | 190 | 阅读`:help cnext`和`help cprevious`。修改你的grep映射,试一下它们。 191 | 192 | 设置`:cnext`和`:cprevious`的映射,让在匹配内容间的移动更加方便。 193 | 194 | 阅读`:help expand`。 195 | 196 | 阅读`:help copen`。 197 | 198 | 在我们创建的映射中加入height参数到`:copen`命令中,看看quickfix窗口能不能以指定的高度打开。 199 | 200 | 阅读`:help silent`。 201 | 202 | -------------------------------------------------------------------------------- /chapters/53.markdown: -------------------------------------------------------------------------------- 1 | 自动加载 2 | =========== 3 | 4 | 我们已经为我们的Potion插件写了大量的功能,覆盖了本书所要讲的内容。 5 | 在结束之前,我们将讲到一些非常重要的方法,可以给我们的插件锦上添花。 6 | 7 | 第一项是使用自动加载让我们的插件更有效率。 8 | 9 | 如何自动加载 10 | ------------------ 11 | 12 | 目前,当用户加载我们的插件时(比如打开了一个Potion文件),所有的功能都会被加载。 13 | 我们的插件还很小,所以这大概不是什么大问题,但对于较大的插件,加载全部代码将会导致可被察觉的卡顿。 14 | 15 | Vim使用称为"自动加载(autoload)"来解决这个问题。自动加载让你直到需要时才加载某一部分代码。 16 | 会有一些性能上的损失,但如果用户不总是需要你的插件的每一行代码,自动加载将带来速度上的飞跃。 17 | 18 | 示范一下它是怎么工作的。看看下面的命令: 19 | 20 | :::vim 21 | :call somefile#Hello() 22 | 23 | 当你执行这个命令,Vim的行为与平常的函数调用有些许不同。 24 | 25 | 如果这个函数已经加载了,Vim简单地像平常一样调用它。 26 | 27 | 否则Vim将在你的`~/.vim`(或`~/.vim/bundles/对应的插件/autoload`)下查找一个叫做`autoload/somefile.vim`的文件。 28 | 29 | 如果文件存在,Vim将加载/source文件。接着Vim就会像平常一样调用它。 30 | 31 | 在这个文件内,函数应该这样定义: 32 | 33 | :::vim 34 | function somefile#Hello() 35 | " ... 36 | endfunction 37 | 38 | 你可以在函数名中使用多个`#`来表示子目录。举个例子: 39 | 40 | :::vim 41 | :call myplugin#somefile#Hello() 42 | 43 | 这将在`autoload/myplugin/somefile.vim`查找自动加载文件。 44 | 里面的函数需要使用自动加载的绝对路径进行定义: 45 | 46 | :::vim 47 | function myplugin#somefile#Hello() 48 | " ... 49 | endfunction 50 | 51 | 实验一下 52 | ------------- 53 | 54 | 为了更好地理解自动加载,让我们实验一下。 55 | 创建一个`~/.vim/autoload/example.vim`文件并加入下面的内容: 56 | 57 | :::vim 58 | echom "Loading..." 59 | 60 | function! example#Hello() 61 | echom "Hello, world!" 62 | endfunction 63 | 64 | echom "Done loading." 65 | 66 | 保存文件并执行`:call example#Hello()`。Vim将输出下面内容: 67 | 68 | :::text 69 | Loading... 70 | Done loading. 71 | Hello, world! 72 | 73 | 这个小演示证明了几件事: 74 | 75 | 1. Vim的确是在半途加载了`example.vim`文件。当我们打开Vim的时候它并不存在,所以不可能是在启动时加载的。 76 | 2. 当Vim找到它需要自动加载的文件后,它在调用对应函数之前就加载了整个文件。 77 | 78 | **先不要关闭Vim**,修改函数的定义成这样: 79 | 80 | :::vim 81 | echom "Loading..." 82 | 83 | function! example#Hello() 84 | echom "Hello AGAIN, world!" 85 | endfunction 86 | 87 | echom "Done loading." 88 | 89 | 保存文件并**不要关闭Vim**,执行`:call example#Hello()`。Vim将简单地输出: 90 | 91 | :::text 92 | Hello, world! 93 | 94 | Vim已经有了`example#Hello`的一个定义,所以它不再需要重新加载文件,这意味着: 95 | 96 | 1. 函数以外的代码将不再执行。 97 | 2. 它不会反映函数本身的变化。 98 | 99 | 现在执行`:call example#BadFunction()`。你将再一次看到加载信息,伴随着一个函数不存在的错误。 100 | 但现在尝试再次执行`:call example#Hello()`。这次你将看到更新后的信息! 101 | 102 | 目前为止你应该清晰地了解到Vim会怎么处理一个自动加载类型的函数调用吧: 103 | 104 | 1. 它首先是否已经存在同名的函数了。如果是,就调用它。 105 | 2. 否则,查找名字对应的文件,并source它。 106 | 3. 然后试图调用那个函数。如果成功,太棒了。如果失败,就输出一个错误。 107 | 108 | 如果你还是没有完成弄懂,回到前面重新过一遍演示,注意观察每条规则生效的地方。 109 | 110 | 自动加载什么 111 | ---------------- 112 | 113 | 自动加载不是没有缺陷的。 114 | 设置了自动加载后,会有一些(小的)运行开销,更别说你不得不在你的代码里容忍丑陋的函数名了。 115 | 116 | 正因为如此,如果你不是写一个用户会在*每次*打开Vim对话时都用到的插件,最好尽量把功能代码都挪到autoload文件中去。 117 | 这将减少你的插件在用户启动Vim时的影响,尤其是在人们安装了越来越多的插件的今天。 118 | 119 | 所以有什么是可以安全地自动加载?那些不由你的用户直接调用的部分。 120 | 映射和自定义命令不能自动加载(因为它们需要由用户调用),但别的许多东西都可以。 121 | 122 | 让我们看看Potion插件里有什么可以自动加载的。 123 | 124 | 在Potion插件里添加自动加载 125 | --------------------------------------- 126 | 127 | 我们将从编译和执行功能开始下手。 128 | 在前一章的最后,我们的`ftplugin/potion/running.vim`文件大概是这样: 129 | 130 | :::vim 131 | if !exists("g:potion_command") 132 | let g:potion_command = "/Users/sjl/src/potion/potion" 133 | endif 134 | 135 | function! PotionCompileAndRunFile() 136 | silent !clear 137 | execute "!" . g:potion_command . " " . bufname("%") 138 | endfunction 139 | 140 | function! PotionShowBytecode() 141 | " Get the bytecode. 142 | let bytecode = system(g:potion_command . " -c -V " . bufname("%")) 143 | 144 | " Open a new split and set it up. 145 | vsplit __Potion_Bytecode__ 146 | normal! ggdG 147 | setlocal filetype=potionbytecode 148 | setlocal buftype=nofile 149 | 150 | " Insert the bytecode. 151 | call append(0, split(bytecode, '\v\n')) 152 | endfunction 153 | 154 | nnoremap r :call PotionCompileAndRunFile() 155 | nnoremap b :call PotionShowBytecode() 156 | 157 | 这个文件仅仅当Potion文件加载时才会调用,所以它通常不会影响Vim的启动时间。 158 | 但可能会有一些用户就是不想要这些功能,所以如果我们可以自动加载某些部分, 159 | 每次打开Potion文件时可以省下他们以毫秒记的时间。 160 | 161 | 是的,这种情况下我们不会节省多少。 162 | 但你可以想象到可能有那么一个插件包括了数千行可以通过自动加载来减少每次的加载时间的代码。 163 | 164 | 让我们开始吧。在你的插件repo中创建一个`autoload/potion/running.vim`文件。 165 | 然后移动两个函数进去,并修改它们的名字,让它们看上去像: 166 | 167 | :::vim 168 | echom "Autoloading..." 169 | 170 | function! potion#running#PotionCompileAndRunFile() 171 | silent !clear 172 | execute "!" . g:potion_command . " " . bufname("%") 173 | endfunction 174 | 175 | function! potion#running#PotionShowBytecode() 176 | " Get the bytecode. 177 | let bytecode = system(g:potion_command . " -c -V " . bufname("%")) 178 | 179 | " Open a new split and set it up. 180 | vsplit __Potion_Bytecode__ 181 | normal! ggdG 182 | setlocal filetype=potionbytecode 183 | setlocal buftype=nofile 184 | 185 | " Insert the bytecode. 186 | call append(0, split(bytecode, '\v\n')) 187 | endfunction 188 | 189 | 注意`potion#running`部分的函数名怎么匹配它们所在的路径。 190 | 现在修改`ftplugin/potion/running.vim`文件成这样: 191 | 192 | :::vim 193 | if !exists("g:potion_command") 194 | let g:potion_command = "/Users/sjl/src/potion/potion" 195 | endif 196 | 197 | nnoremap r 198 | \ :call potion#running#PotionCompileAndRunFile() 199 | 200 | nnoremap b 201 | \ :call potion#running#PotionShowBytecode() 202 | 203 | 保存文件,关闭Vim,然后打开你的`factorial.pn`文件。尝试这些映射,确保它们依然正常工作。 204 | 205 | 确保你仅仅在第一次执行其中一个映射的时候才看到诊断信息`Autoloading...`(你可能需要使用`:message`来看到)。 206 | 一旦认为自动加载正常工作,你可以移除那些信息。 207 | 208 | 正如你看到的,我们保留`nnoremap`映射部分不变。 209 | 我们不能自动加载它们,不然用户就没办法引发自动加载了! 210 | 211 | 你将在Vim插件中普遍看到:大多数的功能将位于自动加载函数中,仅有`nnoremap`和`command`命令每次都被Vim加载。 212 | 每次你写一个有用的Vim插件时,不要忘了这一点。 213 | 214 | 练习 215 | --------- 216 | 217 | 阅读`:help autoload` 218 | 219 | 稍微测试一下并弄懂自动加载变量是怎么一回事。 220 | 221 | 假设你想要强制加载一个Vim已经加载的自动加载文件,并不会惊扰到用户。 222 | 你会怎么做?你可能想要阅读`:help silent!`(译注:此处应该是`:help :silent`)。不过在现实生活中请不要那么做。 223 | 224 | -------------------------------------------------------------------------------- /chapters/33.markdown: -------------------------------------------------------------------------------- 1 | 实例研究:Grep运算符(Operator),第二部分 2 | =================================== 3 | 4 | 目前为止,我们已经完成了一个原型,是时候扩充它,让它更加强大。 5 | 6 | 记住:我们初始目标是创建"grep运算符"。我们还需要做一大堆新的东西来达成目标, 7 | 但要像前一章的过程一样:从简单的东西开始,并逐步改进直到它满足我们的需求。 8 | 9 | 在开始之前,注释掉`~/.vimrc`中在前一章创建的映射。我们还要用同样的快捷键来映射新的运算符。 10 | 11 | 新建一个文件 12 | ------------- 13 | 14 | 创建一个新的运算符需要许多命令,把它们手工打出来将很快变成一种折磨。 15 | 你可以把它附加到`~/.vimrc`,但让我们为这个运算符创建一个独立的文件。我们有足够的必要这么做。 16 | 17 | 首先,找到你的Vim`plugin`文件夹。在Linux或OS X,这将会是`~/.vim/plugin`。 18 | 如果你是Windows用户,它将位于你的主目录下的`vimfiles`文件夹。(如果你找不到,在Vim里使用`:echo $HOME命令) 19 | 如果这个文件夹不存在,创建一个。 20 | 21 | 在`plugin/`下新建文件`grep-operator.vim`。这就是你放置新运算符的代码的地方。 22 | 一旦文件被修改,你可以执行`:source %`来重新加载代码。 23 | 每次你打开Vim,这个文件也会被重新加载,就像`~/.vimrc`。 24 | 25 | 不要忘了,在你source之前,你*必须*先保存文件,这样才能看到变化! 26 | 27 | 骨架(Skeleton) 28 | -------- 29 | 30 | 要创建一个新的Vim运算符,你需要从两个组件开始:一个函数还有一个映射。 31 | 先添加下面的代码到`grep-operator.vim`: 32 | 33 | :::vim 34 | nnoremap g :set operatorfunc=GrepOperatorg@ 35 | 36 | function! GrepOperator(type) 37 | echom "Test" 38 | endfunction 39 | 40 | 保存文件并用`:source %`source它。尝试通过按下`giw`来执行"grep整个词"。 41 | Vim将在接受`iw`动作(motion)后,输出`Test`,意味着我们已经搭起了骨架。 42 | 43 | 函数部分是简单的,没有什么是我们没讲过的。不过映射部分比较复杂。 44 | 我们首先对函数设置了`operatorfunc`选项,然后执行`g@`来以运算符的方式调用这个函数。 45 | 看起来这有点绕,不过这就是Vim工作的原理。 46 | 47 | 暂时把这个映射看作黑魔法吧。稍后你可以到文档里一探究竟。 48 | 49 | 可视模式 50 | ----------- 51 | 52 | 我们已经在normal模式下加入了这个运算符,但还想要在visual模式下用到它。 53 | 在之前的映射下面添加多一个: 54 | 55 | :::vim 56 | vnoremap g :call GrepOperator(visualmode()) 57 | 58 | 保存并source文件。现在在visual模式下选择一些东西并按下`g`。 59 | 什么也没发生,但Vim确实输出了`Test`,所以我们的函数已经运行了。 60 | 61 | 之前我们就见过``,但是还没有解释它是做什么的。试一下在可视模式下选中一些文本并按下`:`。 62 | Vim将打开一个命令行就像平时按下了`:`一样,但是命令行的开头自动添加了`'<,'>`! 63 | 64 | Vim为了提高效率,插入了这些文本来让你的命令在被选择的范围内执行。 65 | 但是这次,我们不需要它添倒忙。我们用``来执行"从光标所在处删除到行首的内容",移除多余文本。 66 | 最后剩下一个孤零零的`:`,为调用`call`命令作准备。 67 | 68 | 我们传递过去的`visualMode()`参数还没有讲过呢。 69 | 这个函数是Vim的内置函数,它返回一个单字符的字符串来表示visual模式的类型: 70 | `"v"`代表字符宽度(characterwise),`"V"`代表行宽度(linewise),`Ctrl-v`代表块宽度(blockwise)。 71 | 72 | 动作类型 73 | ------------ 74 | 75 | 我们定义的函数接受一个`type`参数。我们知道在visual模式下它将会是`visualmode()`的返回值, 76 | 但是在normal模式下呢? 77 | 78 | 编辑函数体部分,让代码像这样: 79 | 80 | :::vim 81 | nnoremap g :set operatorfunc=GrepOperatorg@ 82 | vnoremap g :call GrepOperator(visualmode()) 83 | 84 | function! GrepOperator(type) 85 | echom a:type 86 | endfunction 87 | 88 | Source文件,然后继续并用多种的方式测试它。你可能会得到类似下面的结果: 89 | 90 | * 按下`viwg`显示`v`,因为我们处于字符宽度的visual模式。 91 | * 按下`Vjjg`显示`V`,因为我们处于行宽度的visual模式。 92 | * 按下`giw`显示`char`,因为我们在字符宽度的动作(characterwise motion)中使用该运算符。 93 | * 按下`gG`显示`line`,因为我们在行宽度的动作(linewise motion)中使用该运算符。 94 | 95 | 现在我们已经知道怎么区分不同种类的动作,这对于我们选择需要搜索的词是很重要的。 96 | 97 | 复制文本 98 | ---------------- 99 | 100 | 我们的函数将需要获取用户想要搜索的文本,而这样做最简单的方法就是复制它。 101 | 把函数修改成这样: 102 | 103 | :::vim 104 | nnoremap g :set operatorfunc=GrepOperatorg@ 105 | vnoremap g :call GrepOperator(visualmode()) 106 | 107 | function! GrepOperator(type) 108 | if a:type ==# 'v' 109 | execute "normal! `y" 110 | elseif a:type ==# 'char' 111 | execute "normal! `[v`]y" 112 | else 113 | return 114 | endif 115 | 116 | echom @@ 117 | endfunction 118 | 119 | 哇。好多新的东西啊。试试按下`giw`,`g2e`和`vi(g`看看。 120 | 每次Vim都会输出动作所包括的文本,显然我们已经走上正道了! 121 | 122 | 让我们把这段代码一步步分开来看。首先我们用`if`语句检查`a:type`参数。如果是`'v'`, 123 | 它就是使用在字符宽度的visual模式下,所以我们复制了可视模式下的选中文本。 124 | 125 | 注意我们使用大小写敏感比较`==#`。如果我们只用了`==`而用户设置`ignorecase`, 126 | `"V"`也会是匹配的,结果*不会*如我们所愿。重视防御性编程! 127 | 128 | `if`语句的第二个分支则会拦住normal模式下使用字符宽度的动作。 129 | 130 | 剩下的情况只是默默地退出。我们直接忽略行宽度/块宽度的visual模式和对应的动作类型。 131 | Grep默认情况下不会搜索多行文本,所以在搜索内容中夹杂着换行符是毫无意义的。 132 | 133 | 我们每一个`if`分支都会执行`normal!`命令来做两件事: 134 | 135 | * 在可视状态下选中我们想要的文本范围: 136 | * 先移动到范围开头,并标记 137 | * 进入字符宽度的visual模式 138 | * 移动到范围结尾的标记 139 | * 复制可视状态下选中的文本。 140 | 141 | 先不要纠结于特殊标记方式。你将会在完成本章结尾的练习时学到为什么它们会不一样。 142 | 143 | 函数的最后一行输出变量`@@`。不要忘了以`@`开头的变量是寄存器。`@@`是"未命名"(unnamed)寄存器: 144 | 如果你在删除或复制文本时没有指定一个寄存器,Vim就会把文本放在这里。 145 | 146 | 简明扼要地说:我们选中要搜索的文本,复制它,然后输出被复制的文本。 147 | 148 | 转义搜索文本 149 | ------------------------ 150 | 151 | 既然得到了Vim字符串形式的需要的文本,我们可以像前一章一样将它转义。修改`echom`命令成这样: 152 | 153 | :::vim 154 | nnoremap g :set operatorfunc=GrepOperatorg@ 155 | vnoremap g :call GrepOperator(visualmode()) 156 | 157 | function! GrepOperator(type) 158 | if a:type ==# 'v' 159 | normal! `y 160 | elseif a:type ==# 'char' 161 | normal! `[v`]y 162 | else 163 | return 164 | endif 165 | 166 | echom shellescape(@@) 167 | endfunction 168 | 169 | 保存并source文件,然后在可视模式下选中带特殊字符的文本,按下`g`。 170 | Vim显示一个被转义了的能安全地传递给shell命令的文本。 171 | 172 | 执行Grep 173 | ------------ 174 | 175 | 我们终于可以加上`grep!`命令来实现真正的搜索。替换掉`echom`那一行,代码看起来就像这样: 176 | 177 | :::vim 178 | nnoremap g :set operatorfunc=GrepOperatorg@ 179 | vnoremap g :call GrepOperator(visualmode()) 180 | 181 | function! GrepOperator(type) 182 | if a:type ==# 'v' 183 | normal! `y 184 | elseif a:type ==# 'char' 185 | normal! `[v`]y 186 | else 187 | return 188 | endif 189 | 190 | silent execute "grep! -R " . shellescape(@@) . " ." 191 | copen 192 | endfunction 193 | 194 | 看起来眼熟吧。我们简单地执行上一章得到的`silent execute "grep! ..."`命令。 195 | 由于我们不再把所有的代码塞进单个`nnoremap`命令里,现在代码甚至更加清晰易懂了! 196 | 197 | 保存并source文件,然后尝试一下,享受自己辛勤劳动的成果吧! 198 | 199 | 因为定义了一个全新的Vim运算符,现在我们可以在许多场景下使用它了,比如: 200 | 201 | * `viwg`: 可视模式下选中一个词,然后grep它。 202 | * `g4w`: Grep接下来的四个词。 203 | * `gt;`: Grep到分号为止的文本。 204 | * `gi[`: Grep方括号里的文本. 205 | 206 | 这里彰显了Vim的优越性:它的编辑命令就像一门语言。当你加入新的动词,它会自动地跟(大多数)现存的名词和形容词搭配起来。 207 | 208 | 练习 209 | --------- 210 | 211 | 阅读`:help visualmode()`。 212 | 213 | 阅读`:help c_ctrl-u`。 214 | 215 | 阅读`:help operatorfunc`。 216 | 217 | 阅读`:help map-operator`。 218 | -------------------------------------------------------------------------------- /chapters/52.markdown: -------------------------------------------------------------------------------- 1 | 外部命令 2 | ================= 3 | 4 | Vim遵循UNIX哲学"做一件事,做好它"。 5 | 与其试图集成你可能想要的功能到编辑器自身,更好的办法是在适当时使用Vim来调用外部命令。 6 | 7 | 让我们在插件中添加一些跟Potion编译器交互的命令,来浅尝在Vim里面调用外部命令的方法。 8 | 9 | 编译 10 | --------- 11 | 12 | 首先我们将加入一个命令来编译和执行当前Potion文件。 13 | 有很多方法可以实现这一点,不过我们暂且用外部命令简单地实现。 14 | 15 | 在你的插件的repo中创建`potion/ftplugin/potion/running.vim`文件。 16 | 这将是我们创建编译和执行Potion文件的映射的地方。 17 | 18 | :::vim 19 | if !exists("g:potion_command") 20 | let g:potion_command = "potion" 21 | endif 22 | 23 | function! PotionCompileAndRunFile() 24 | silent !clear 25 | execute "!" . g:potion_command . " " . bufname("%") 26 | endfunction 27 | 28 | nnoremap r :call PotionCompileAndRunFile() 29 | 30 | 第一部分以全局变量的形式储存着用来执行Potion代码的命令,如果还没有设置过的话。 31 | 我们之前见过类似的检查了。 32 | 33 | 如果`potion`不在用户的`$PATH`内,这将允许用户覆盖掉它, 34 | 比如在`~/.vimrc`添加类似`let g:potion_command = "/Users/sjl/src/potion/potion"`的一行。 35 | 36 | 最后一行添加了一个buffer-local的映射来调用我们前面定义的函数。 37 | 不要忘了,由于这个文件位于`ftdetect/potion`文件夹,每次一个文件的`filetype`设置成`potion`,它都会被执行。 38 | 39 | 真正实现了功能的地方在`PotionCompileAndRunFile()`。 40 | 保存文件,打开`factorial.pn`并按下`r`来执行这个映射,看看发生了什么。 41 | 42 | 如果`potion`位于你的`$PATH`下,代码会被执行,你应该在终端看到输出(或者在窗口底部,如果你用的是GUI vim)。 43 | 如果你看到了没有找到`potion`命令的错误,你需要像上面提到那样在`~/.vimrc`内设置`g:potion_command`。 44 | 45 | 让我们了解一下`PotionCompileAndRunFile()`的工作原理。 46 | 47 | Bang! 48 | ----- 49 | 50 | `:!`命令(念作"bang")会执行外部命令并在屏幕上显示它们的输出。尝试执行下面的命令: 51 | 52 | :::vim 53 | :!ls 54 | 55 | Vim将输出`ls`命令的结果,同时还有"请按 ENTER 或其它命令继续"的提示。 56 | 57 | 当这样执行时,Vim不会传递任何输入给外部命令。为了验证,执行: 58 | 59 | :::vim 60 | :!cat 61 | 62 | 打入一些行,然后你将看到`cat`命令把它们都吐回来了,就像你是在Vim之外执行`cat`。按下Ctrl-D来结束。 63 | 64 | 想要执行一个外部命令并避免`请按 ENTER 或其它命令继续`的提示,使用`:silent !`。执行下面的命令: 65 | 66 | :::vim 67 | :silent !echo Hello, world. 68 | 69 | 如果在GUI Vim比如MacVim或gVim下执行,你将不会看到`Hello,world.`的输出。 70 | 71 | 如果你在终端下执行,你看到的结果取决于你的配置。 72 | 一旦执行了一个`:silent !`,你可能需要执行`:redraw!`来重新刷新屏幕。 73 | 74 | 注意这个命令是`:silent !`而不是`:silent!`(看到空格了吗?)! 75 | 这是两个不一样的命令,我们想要的是前者!Vimscript奇妙吧? 76 | 77 | 让我们回到`PotionCompileAndRun()`上来: 78 | 79 | :::vim 80 | function! PotionCompileAndRunFile() 81 | silent !clear 82 | execute "!" . g:potion_command . " " . bufname("%") 83 | endfunction 84 | 85 | 首先我们执行一个`silent !clear`命令,来清空屏幕输出并避免产生提示。 86 | 这将确保我们仅仅看到本次命令的输出,如果一再执行同样的命令,你会觉得有用的。 87 | 88 | 在下一行我们使用老朋友`execute`来动态创建一个命令。建立的命令看上去类似于: 89 | 90 | :::text 91 | !potion factorial.pn 92 | 93 | 注意这里没有`silent`,所以用户将看到命令输出,并不得不按下enter来返回Vim。 94 | 这就是我们想要的,所以就这样设置好了。 95 | 96 | 显示字节码 97 | ------------------- 98 | 99 | Potion编译器有一个显示由它生成的字节码的选项。如果你正试图在非常低级的层次下debug,这将帮上忙。 100 | 在shell里执行下面的命令: 101 | 102 | :::text 103 | potion -c -V factorial.pn 104 | 105 | 你将看到一大堆像这样的输出: 106 | 107 | :::text 108 | -- parsed -- 109 | code ... 110 | -- compiled -- 111 | ; function definition: 0x109d6e9c8 ; 108 bytes 112 | ; () 3 registers 113 | .local factorial ; 0 114 | .local print_line ; 1 115 | .local print_factorial ; 2 116 | ... 117 | [ 2] move 1 0 118 | [ 3] loadk 0 0 ; string 119 | [ 4] bind 0 1 120 | [ 5] loadpn 2 0 ; nil 121 | [ 6] call 0 2 122 | ... 123 | 124 | 让我们添加一个使用户可以在新的Vim分割下,看到当前Potion代码生成的字节码的映射, 125 | 这样他们能更方便地浏览并测试输出。 126 | 127 | 首先,在`ftplugin/potion/running.vim`底部添加下面一行: 128 | 129 | 130 | :::vim 131 | nnoremap b :call PotionShowBytecode() 132 | 133 | 这里没有什么特别的 -- 只是一个简单的映射。现在先描划出函数的大概框架: 134 | 135 | :::vim 136 | function! PotionShowBytecode() 137 | " Get the bytecode. 138 | 139 | " Open a new split and set it up. 140 | 141 | " Insert the bytecode. 142 | 143 | endfunction 144 | 145 | 既然已经建立起一个框架,让我们把它变成现实吧。 146 | 147 | system() 148 | -------- 149 | 150 | 有许多不同的方法可以实现这一点,所以我选择相对便捷的一个。 151 | 152 | 执行下面的命令: 153 | 154 | :::vim 155 | :echom system("ls") 156 | 157 | 你应该在屏幕的底部看到`ls`命令的输出。如果执行`:message`,你也能看到它们。 158 | Vim函数`system()`接受一个字符串命令作为参数并以字符串形式返回那个命令的输出。 159 | 160 | 你可以把另一个字符串作为参数传递给`system()`。执行下面命令: 161 | 162 | :::vim 163 | :echom system("wc -c", "abcdefg") 164 | 165 | Vim将显示`7`(以及一些别的)。 166 | 如果你像这样传递第二个参数,Vim将写入它到临时文件中并通过管道作为标准输入输入到命令里。 167 | 目前我们不需要这个特性,不过它值得了解。 168 | 169 | 回到我们的函数。编辑`PotionShowBytecode()`来填充框架的第一部分: 170 | 171 | :::vim 172 | function! PotionShowBytecode() 173 | " Get the bytecode. 174 | let bytecode = system(g:potion_command . " -c -V " . bufname("%")) 175 | echom bytecode 176 | 177 | " Open a new split and set it up. 178 | 179 | " Insert the bytecode. 180 | 181 | endfunction 182 | 183 | 保存文件,在`factorial.pn`处执行`:set ft=potion`重新加载,并使用`b`尝试一下。 184 | Vim会在屏幕的底部显示字节码。一旦看到它成功执行了,你可以移除`echom`。 185 | 186 | 在分割上打草稿 187 | -------------- 188 | 189 | 接下来我们将打开一个新的分割把结果展示给用户。 190 | 这将让用户能够借助Vim的全部功能来浏览字节码,而不是仅仅只在屏幕上昙花一现。 191 | 192 | 为此我们将创建一个"草稿"分割:一个分割,它包括一个永不保存并每次执行映射都会被覆盖的缓冲区。 193 | 把`PotionShowBytecode()`函数改成这样: 194 | 195 | :::vim 196 | function! PotionShowBytecode() 197 | " Get the bytecode. 198 | let bytecode = system(g:potion_command . " -c -V " . bufname("%")) 199 | 200 | " Open a new split and set it up. 201 | vsplit __Potion_Bytecode__ 202 | normal! ggdG 203 | setlocal filetype=potionbytecode 204 | setlocal buftype=nofile 205 | 206 | " Insert the bytecode. 207 | 208 | endfunction 209 | 210 | 新增的命令应该很好理解。 211 | 212 | `vsplit`创建了名为`__Potion_Bytecode__`的新竖直分割。 213 | 我们用下划线包起名字,使得用户注意到这不是普通的文件(它只是显示输出的缓冲区)。 214 | 下划线不是什么特殊用法,只是约定俗成罢了。 215 | 216 | 接着我们用`normal! ggdG`删除缓冲区中的所有东西。 217 | 第一次执行这个映射时,并不需要这样做,但之后我们将重用`__Potion_Bytecode__`缓冲区,所以需要清空它。 218 | 219 | 接下来我们为这个缓冲区设置两个本地设置。首先我们设置它的文件类型为`potionbytecode`,只是为了指明它的用途。 220 | 我们也改变`buftype`为`nofile`,告诉Vim这个缓冲区与磁盘上的文件不相关,这样它就不会把缓冲区写入。 221 | 222 | 最后还剩下把我们保存在`bytecode`变量的字节码转储进缓冲区。完成函数,让它看上去像这样: 223 | 224 | :::vim 225 | function! PotionShowBytecode() 226 | " Get the bytecode. 227 | let bytecode = system(g:potion_command . " -c -V " . bufname("%") . " 2>&1") 228 | 229 | " Open a new split and set it up. 230 | vsplit __Potion_Bytecode__ 231 | normal! ggdG 232 | setlocal filetype=potionbytecode 233 | setlocal buftype=nofile 234 | 235 | " Insert the bytecode. 236 | call append(0, split(bytecode, '\v\n')) 237 | endfunction 238 | 239 | Vim函数`append()`接受两个参数:一个将被附加内容的行号和一个将按行附加的字符串列表。 240 | 举个例子,尝试执行下面命令: 241 | 242 | :::vim 243 | :call append(3, ["foo", "bar"]) 244 | 245 | 这将附加两行,`foo`和`bar`,在你当前缓冲区的第三行之后。 246 | 这次我们将在表示文件开头的第0行之后添加。 247 | 248 | 我们需要一个字符串列表来附加,但我们只有来自`system()`调用的单个包括换行符的字符串。 249 | 我们使用Vim的`split()`函数来分割这一大坨文本成一个字符串列表。 250 | `split()`接受一个待分割的字符串和一个查找分割点的正则表达式。这真的很简单。 251 | 252 | 现在函数已经完成了,试一下对应的映射。 253 | 当你在`factorial.pn`中执行`b`,Vim将打开新的包括Potion字节码的缓冲区。 254 | 修改Potion源代码,保存文件,执行映射来看看会有什么不同的结果。 255 | 256 | 练习 257 | --------- 258 | 259 | 阅读`:help bufname`。 260 | 261 | 阅读`:help buftype`。 262 | 263 | 阅读`:help append()`。 264 | 265 | 阅读`:help split()`。 266 | 267 | 阅读`:help :!`。 268 | 269 | 阅读`:help :read`和`:help :read!`(我们没有讲到这些命令,不过它们非常好用)。 270 | 271 | 阅读`:help system()`。 272 | 273 | 阅读`:help design-not`。 274 | 275 | 目前,我们的插件要求用户在执行映射之前手动保存文件来使得他们的改变起效。 276 | 当今撤销已经变得非常轻易,所以修改写过的函数来自动替他们保存。 277 | 278 | 如果你在一个带语法错误的Potion文件上执行这个字节码映射,会发生什么?为什么? 279 | 280 | 修改`PotionShowBytecode()`函数来探测Potion编译器是否返回一个错误,并向用户输出错误信息。 281 | 282 | 加分题 283 | ------------ 284 | 285 | 每次你执行字节码映射时,一个新的竖直分割都会被创建,即使用户没有关闭上一个。 286 | 如果用户没有一再关闭这些窗口,他们最终将被大量额外的窗口困住。 287 | 288 | 修改`PotionShowBytecode()`来探测`__Potion_Bytecode__`缓冲区的窗口是否已经打开了, 289 | 如果是,切换到它上去而不是创建新的分割。 290 | 291 | 你大概想要阅读`:help bufwinnr()`来获取帮助。 292 | 293 | 额外的加分题 294 | ----------------- 295 | 296 | 还记得我们设置临时缓冲区的`filetype`为`potionbytecode`? 297 | 创建`syntax/potionbytecode.vim`文件并为Potion字节码定义语法高亮,使得它们更易读。 298 | 299 | -------------------------------------------------------------------------------- /chapters/54.markdown: -------------------------------------------------------------------------------- 1 | 文档 2 | ============= 3 | 4 | 我们的Potion插件有着许多有用的功能,但是无人知晓这一点,除非我们留下了文档! 5 | 6 | Vim自身的文档非常棒。它不仅是详细地,而且也是非常透彻的。 7 | 同时,它也启发了许多插件作者写出很好的插件文档,结果是在Vimscript社区里营造出强大的文档文化。 8 | 9 | 如何使用文档 10 | ----------------------- 11 | 12 | 当你阅读Vim里的`:help`条目时,你显然注意到了有些内容被高亮得跟别的不一样。 13 | 让我们看一下这是怎么回事。 14 | 15 | 打开任何帮助条目(比如`:help help`)并执行`:set filetype?`。Vim显示`filetype=help`。 16 | 现在执行`:set filetype=text`,你将看到高亮消失了。 17 | 再次执行`:set filetype=help`,高亮又回来了。 18 | 19 | 这表明Vim帮助文件只是获得了语法高亮的文本文件,一如其他的文件格式! 20 | 这意味着你可以照着写并获得同样的高亮。 21 | 22 | 在你的插件repo中创建名为`doc/potion.txt`文件。 23 | Vim/Pathogen在索引帮助条目时查找在`doc`文件夹下的文件,所以我们在此写下插件的帮助文档。 24 | 25 | 用Vim打开这个文件并执行`:set filetype=help`,这样你在输入的时候就可以看到语法高亮。 26 | 27 | 帮助的题头 28 | ----------- 29 | 30 | 帮助文件的格式是一个取决于个人品味的问题。 31 | 尽管这么说,我还是讲讲一种在当前的Vimscript社区逐渐被广泛使用的文档格式。 32 | 33 | 文件的第一行应该包括帮助文件的文件名,接下来是一行对插件功能的描述。 34 | 在你的`potion.txt`文件的第一行加上下面的内容: 35 | 36 | :::text 37 | *potion.txt* functionality for the potion programming language 38 | 39 | 在帮助文件中用星号括起一个词创建了一个可以用于跳转的"tag"。 40 | 执行`:Helptags`来让Pathogen重新索引帮助tags,接着打开一个新的Vim窗口并执行`:help potion.txt`。 41 | Vim将打开你的帮助文档,一如往日打开别人写的。 42 | 43 | 接下来你应该把你的插件的大名和一个老长老长的描述打上去。 44 | 有些作者(包括我)喜欢在这上面花点心思,用一些ASCII艺术字点缀一下。 45 | 在`potion.txt`文件内加上一个不错的标题节: 46 | 47 | :::text 48 | *potion.txt* functionality for the potion programming language 49 | 50 | ___ _ _ ~ 51 | / _ \___ | |_(_) ___ _ __ ~ 52 | / /_)/ _ \| __| |/ _ \| '_ \ ~ 53 | / ___/ (_) | |_| | (_) | | | | ~ 54 | \/ \___/ \__|_|\___/|_| |_| ~ 55 | 56 | Functionality for the Potion programming language. 57 | Includes syntax highlighting, code folding, and more! 58 | 59 | 我是通过执行`figlet -f ogre "Potion"`命令来得到这些有趣的字符的。 60 | [Figlet][]是一个好玩的小程序,可以生成ACSII艺术字。 61 | 每一行结尾的`~`字符保证Vim不会尝试高亮或隐藏艺术字中的某些字符。 62 | 63 | [Figlet]: http://www.figlet.org/ 64 | 65 | 写什么文档 66 | ---------------- 67 | 68 | 接下来通常是一个内容目录。不过,首先,让我们决定我们想要写的文档内容。 69 | 70 | 在给一个新插件写文档时,我通常从下面的列表开始: 71 | 72 | * Introduction 73 | * Usage 74 | * Mappings 75 | * Configuration 76 | * License 77 | * Bugs 78 | * Contributing 79 | * Changelog 80 | * Credits 81 | 82 | 如果这是一个大插件,需要一个"大纲",我将写一个介绍段落来概括它的主要功能。 83 | 否则我会跳过它继续下一段。 84 | 85 | 一个"usage"段落应该解释,大体上用户将怎样*使用*你的插件。如果他们需要通过映射进行交互,告诉他们。 86 | 如果映射数目不是很多,你可以简单地在这里列出来,否则你可能需要创建一个单独的"mappings"段落来一一列出。 87 | 88 | "configuration"段落应该详尽列出用户可以自定义的变量,以及它们的功能和默认值。 89 | 90 | "license"段落应该指出插件代码所用的协议,连同一个指向协议完整文本的URL。 91 | 不要把整份协议放入帮助文件 -- 大多数用户知道这些常用的协议是什么意思,这样做只会徒增混乱。 92 | 93 | "bugs"段落应该尽可能短小。列出所有你已知却尚未解决的主要的bugs,并告知用户如何向你报告他们抓到的新bug。 94 | 95 | 如果你希望你的用户可以向项目奉献bug fixes和features,他们需要怎么做。 96 | 应该把pull request发到GitHub呢?还是Bitbucket?要用email寄补丁吗? 97 | 要选择其中的一个还是全都得要?一个"contributing"段落可以清楚地表明你想要接受代码的方式。 98 | 99 | 一个changlog也是值得包含在内的,这样当用户从版本X更新到版本Y时,他们立即可以看到什么改变了。 100 | 此外,我强烈推荐你为你的插件使用一个合理的版本号计划,比如[Semantic Versioning][],并一直坚持。 101 | 你的用户会感谢你的。 102 | 103 | 最后,我喜欢加入一个"credits"段落来留下自己的大名,列出影响该插件创作的其他插件,感谢奉献者,等等。 104 | 105 | 这样我们就有一个成功的开始了。对于许多特殊的插件,你可能觉得需要不这么做,那也没问题。 106 | 你仅需追随下面几条规则: 107 | 108 | * 透彻明了 109 | * 不要废话 110 | * 带领你的用户漫步于你的插件,从一无所知到了如指掌。 111 | 112 | [Semantic Versioning]: http://semver.org/ 113 | 114 | 内容目录 115 | ----------------- 116 | 117 | 既然我们已经了解了应该要有的段落,把下面内容加入到`potion.txt`文件中: 118 | 119 | :::text 120 | ==================================================================== 121 | CONTENTS *PotionContents* 122 | 123 | 1. Usage ................ |PotionUsage| 124 | 2. Mappings ............. |PotionMappings| 125 | 3. License .............. |PotionLicense| 126 | 4. Bugs ................. |PotionBugs| 127 | 5. Contributing ......... |PotionContributing| 128 | 6. Changelog ............ |PotionChangelog| 129 | 7. Credits .............. |PotionCredits| 130 | 131 | 有很多事情需要在这里提一下。 132 | 首先,`=`字符组成的那行将被高亮。你可以用这些行醒目地隔开你的帮助文档各部分。 133 | 你也可以使用`-`隔开子段落,如果想要的话。 134 | 135 | `*PotionContents*`将创建另一个tag,这样用户可以输入`:help PotionContents`来直接跳到内容目录。 136 | 137 | 用`|`包起每一个词将创建一个跳转到tag的链接。 138 | 用户可以把他们的光标移到词上并按下``跳转到对应tag,就像他们输入`:help TheTag`一样。 139 | 他们也可以用鼠标点开。 140 | 141 | Vim将隐藏`*`和`|`并高亮其间的内容,所以用户将会看到一个整洁漂亮的目录,以便于跳到感兴趣的地方。 142 | 143 | 段落 144 | -------- 145 | 146 | 你可以像这样创建一个段头: 147 | 148 | :::text 149 | ==================================================================== 150 | Section 1: Usage *PotionUsage* 151 | 152 | This plugin with automatically provide syntax highlighting for 153 | Potion files (files ending in .pn). 154 | 155 | It also... 156 | 157 | 确保对`*`围起的内容创建了正确的tags,这样你的目录的链接才能正常工作。 158 | 159 | 继续并为目录中每一部分创建段头。 160 | 161 | 例子 162 | -------- 163 | 164 | 我可以讲述所有的帮助文件语法和怎样使用它们,但我不喜欢这样。 165 | 所以,不如我给你一系列不同类型的Vim插件文档作为例子。 166 | 167 | 对于每个例子,复制文档源代码到一个Vim缓冲区并设置它的filetype为`help`来观察它的渲染。 168 | 如果你想比较每个渲染效果,切回`text`看看。 169 | 170 | 你也许需要使用你的Vimscript技能为当前缓冲区创建一个切换于`help`和`text`的映射。 171 | 172 | * [Clam][],我自己用来写shell命令的插件。这是一个很小的范例,满足了我前面讲过的大多数内容。 173 | * [NERD tree][],Scrooloose写的一个文件浏览插件。 174 | 注意大体结构,还有他如何在详尽解释每一项之前,总结出一个易读的列表。 175 | * [Surround][],Tim Pope写的一个处理环绕字符的插件。 176 | 注意到它没有目录,以及不同的段头风格和表格列项(table column headers)。 177 | 弄懂他是怎么做到的,并想想你是否喜欢这种风格。这是个人风格问题啦。 178 | * [Splice][],我自己用来解决版本控制中[three-way merge conflict][]的插件。 179 | 注意映射列表的排版方式,以及我怎样使用ASCII流派的图片来解释布局。有时候,一图胜千言。 180 | 181 | 不要忘了,Vim本身的文档也可以作为一个例子。这会给你许多可供学习的内容。 182 | 183 | [Clam]: https://github.com/sjl/clam.vim/blob/master/doc/clam.txt 184 | [Surround]: https://github.com/tpope/vim-surround/blob/master/doc/surround.txt 185 | [NERD tree]: https://github.com/scrooloose/nerdtree/blob/master/doc/NERD_tree.txt 186 | [Splice]: https://github.com/sjl/splice.vim/blob/master/doc/splice.txt 187 | [three-way merge conflict]: http://en.wikipedia.org/wiki/Merge_(revision_control)#Three-way_merge 188 | 189 | 写! 190 | ------ 191 | 192 | 既然你已经看过其他插件如何规划和撰写它们的文档,给你的Potion插件填补上文档内容吧。 193 | 194 | 如果你不熟悉技术文档的写作,这可能会是个挑战。 195 | 学习如何去写并不容易,但一如其他技能,它需要的是更多的练习,所以现在开始吧! 196 | 你不必苛求完美,从战争中学习战争即可。 197 | 198 | 不要惧于写你没有完全弄懂的事情,待会丢掉它重写即可。 199 | 经常只要在缓冲区中*信手留下几笔*,将会带动你的头脑进入写作状态。 200 | 任何时候你想重起炉灶,旧版本一直会在版本控制中等你。 201 | 202 | 一个开始的好方法是想象你身边也有一个使用Vim的好基友。 203 | 他对你的插件很感兴趣却未曾用过,而你的目标是让他熟练掌握。 204 | 在你写下插件工作的方式之前,考虑如何向人类进行介绍,可以让你脚踏实地,避免太深入于技术层面。 205 | 206 | 如果你依旧卡住了,感觉自己无力应对写一个完整插件的文档的挑战,尝试做点简单的。 207 | 在你的`~/.vimrc`中找一个映射并给它写下完整的文档。解释它是干什么的,怎么用它,它怎么工作。 208 | 比如,这是我的`~/.vimrc`的一个例子: 209 | 210 | :::vim 211 | " "Uppercase word" mapping. 212 | " 213 | " This mapping allows you to press in insert mode to convert the 214 | " current word to uppercase. It's handy when you're writing names of 215 | " constants and don't want to use Capslock. 216 | " 217 | " To use it you type the name of the constant in lowercase. While 218 | " your cursor is at the end of the word, press to uppercase it, 219 | " and then continue happily on your way: 220 | " 221 | " cursor 222 | " v 223 | " max_connections_allowed| 224 | " 225 | " MAX_CONNECTIONS_ALLOWED| 226 | " ^ 227 | " cursor 228 | " 229 | " It works by exiting out of insert mode, recording the current cursor 230 | " location in the z mark, using gUiw to uppercase inside the current 231 | " word, moving back to the z mark, and entering insert mode again. 232 | " 233 | " Note that this will overwrite the contents of the z mark. I never 234 | " use it, but if you do you'll probably want to use another mark. 235 | inoremap mzgUiw`za 236 | 237 | 它比一个完整插件的文档短很多,却是一个练习写作的好练习。 238 | 如果你把`~/.vimrc`放到Bitbucket或GitHub,别人也更容易理解。 239 | 240 | 练习 241 | --------- 242 | 243 | 给Potion插件每一部分写下文档。 244 | 245 | 阅读`:help help-writing`来帮助你写帮助文档。 246 | 247 | 阅读`:help :left`, `:help :right`,和`:help :center`来学习三个有用的命令使得你的ASCII艺术字更好看。 248 | 249 | -------------------------------------------------------------------------------- /chapters/51.markdown: -------------------------------------------------------------------------------- 1 | Potion段移动 2 | ======================= 3 | 4 | 既然知道了段移动的工作原理,让我们重新映射这些命令来使得它们对于Potion文件起作用。 5 | 6 | 首先我们要决定Potion文件中"段"的意义。 7 | 有两对段移动命令,所以我们可以总结出两套组合,我们的用户可以选择自己喜欢的一个。 8 | 9 | 让我们使用下面两个组合来决定哪里是Potion中的段: 10 | 11 | 1. 任何在空行之后的,第一个字符为非空字符的行,以及文件首行。 12 | 2. 任何第一个字符为非空字符,包括一个等于号,并以冒号结尾的行。 13 | 14 | 稍微拓展我们的`factorial.pn`例子,这就是那些规则当作段头的地方: 15 | 16 | :::text 17 | # factorial.pn 1 18 | # Print some factorials, just for fun. 19 | 20 | factorial = (n): 1 2 21 | total = 1 22 | 23 | n to 1 (i): 24 | total *= i. 25 | 26 | total. 27 | 28 | print_line = (): 1 2 29 | "-=-=-=-=-=-=-=-\n" print. 30 | 31 | print_factorial = (i): 1 2 32 | i string print 33 | '! is: ' print 34 | factorial (i) string print 35 | "\n" print. 36 | 37 | "Here are some factorials:\n\n" print 1 38 | 39 | print_line () 1 40 | 10 times (i): 41 | print_factorial (i). 42 | print_line () 43 | 44 | 我们的第一个定义更加自由。它定义一个段为一个"顶级的文本块"。 45 | 46 | 第二个定义则严格一点。它定义一个段为一个函数定义。 47 | 48 | 自定义映射 49 | --------------- 50 | 51 | 在你的插件的repo中创建`ftplugin/potion/sections.vim`。 52 | 这将是我们放置段移动代码的地方。记得一旦一个缓冲区的`filetype`设置为`potion`,这里的代码就会执行。 53 | 54 | 我们将重新映射全部四个段移动命令,所以继续并创建一个骨架: 55 | 56 | :::vim 57 | noremap