还剩下什么?

最后更新于:2022-04-01 21:06:10

如果已经读到了这里并且完成了所有的例子和练习,你现在对Vimscript基础的掌握就很牢固了。 不要担心,还有_许多_东西需要学呢! 如果你求知若渴,这里还有一些东西值得你去探索。 ## 配色方案 在本书中我们给Potion文件添加了语法高亮。作为硬币的另一面,我们也可以创建配色方案来决定每种语法元素的颜色。 制作Vim的配色方案非常简单直白,甚至有点重复。阅读`:help highlgiht`来学习基础知识。 你可能想要看看一些内置的配色方案来看他们怎么组织文件的。 如果你渴望挑战,看看我自己的[灰太狼](https://github.com/sjl/badwolf/blob/master/colors/badwolf.vim)配色方案来了解我是怎么用Vimscript来为我简化定义及维护工作的。 注意"palette"字典和`HL`函数,它们动态地生成`highlight`命令。 ## Command命令 许多插件允许用户使用键映射和函数调用来交互,但有一些偏好使用Ex命令。 举个例子,[Fugitive](https://github.com/tpope/vim-fugitive)插件创建类似`:Gbrowse`和`:Gdiff`并把调用它们的方式留给用户定制。 像这样的命令是通过`:command`命令创建的。阅读`:help user-commands`来学习怎样给自己制作一个。 你应该已经学会了足够的Vimscript知识来帮助自己理解Vim文档,并以此来学习新的命令。 ## 运行时路径 在本书中,关于Vim怎么加载某个文件时,我都是用"使用Pathogen"应付过去的。 鉴于你已经懂得了许多Vimscript知识,你可以阅读`:help runtimepath`并查看[Pathogen源代码](https://github.com/tpope/vim-pathogen/blob/master/autoload/pathogen.vim) 来找出幕后隐藏的真相。 ## Omnicomplete Vim提供了许多不同的方法来补全文本(浏览`:help ins-completion`)。 大多数都很简单,但其中最强大的是"omnicomplete", 它允许你调用一个自定义的Vimscript函数来决定你想到的各种补全方式。 当你决定对omnicomplete一探究竟,你可以从`:help omnifunc`和`:help coml-omni`开始你的征途。 ## 编译器支持 在我们的Potion插件中,我们创建了一些编译并执行Potion文件的映射。 Vim提供了更深入的支持来跟编译器交互,包括解析编译器错误并生成一个整洁的列表让你跳转到对应的错误。 如果你对此感兴趣,你可以从通读整篇`:help quickfix.txt`开始深入。 不过,我得提醒你`errorformat`不适合心脏虚弱的人阅读。 ## 其他语言 这本书专注于Vimscript,但Vim也提供了其他语言的接口,比如Python, Ruby, 和Lua。 这意味着如果不喜欢Vimscript,你可以使用其他语言拓展Vim。 当然还是需要了解Vimscript来编辑你的`~/.vimrc`,和理解Vim提供给其他语言的API。 但使用一个替代语言可能是从Vimscript的局限之处解放出来的好办法,尤其在写大型插件的时候。 如果你想了解更多用特定语言拓展Vim,查看下列对应的帮助文档: * `:help Python` * `:help Ruby` * `:help Lua` * `:help perl-using` * `:help MzScheme` ## Vim文档 作为最后的部分,这里列出了一些Vim帮助条目,它们非常有用,有趣,有道理,或者仅仅是好玩(排名不分先后): * `:help various-motions` * `:help sign-support` * `:help virtualedit` * `:help map-alt-keys` * `:help error-messages` * `:help development` * `:help tips` * `:help 24.8` * `:help 24.9` * `:help usr_12.txt` * `:help usr_26.txt` * `:help usr_32.txt` * `:help usr_42.txt` ## 练习 去为你想要的功能写一个Vim插件,向全世界分享你的成果!
';

发布

最后更新于:2022-04-01 21:06:07

现在你拥有了足够的Vimscript技能来打造能帮助许多人的Vim插件。 这一章涉及如何把你的插件发布在网上,以便人们获取,还有如何向潜在用户派小广告。 ## 托管 你需要做的第一件事是把你的插件放在网上,让其他人可以下载它。 最普遍的选择是放到[Vim官网的script版面](http://www.vim.org/scripts/)。 你需要这个网站的一个免费账户。一旦你有了,你可以点击"Add Script"链接并填写表单。 到那里你就会明白了。 在过去的几年中有一个趋势,越来越多的插件托管在类似Bitbucket或GitHub的网络集市上。 这种情况可能由于两个因素。首先,Pathogen使得每一个被安装的插件的文件不需要放在单独的位置。 像Mercurial和Git这样的分布式版本控制系统以及像Bitbucket和GitHub这样的公共托管网站的崛起对此也有影响。 提供代码仓库对于想要用版本控制管理自己的dotfiles的人来说是十分方便的。 Mercurial用户可以使用Mercurial的"subrepositories"来跟踪插件版本的变化, 而Git用户可以使用submodules(尽管只能对其他Git代码仓库起作用,这跟Mercurial的subrepo不一样)。 对你安装的每一个插件有一个完整的仓库,也使得当发现它们出现问题时debug更简单。 你可以使用blame, bisection或其他你的VCS提供的工具来找出哪里的问题。 如果你在自己的机器上有一个仓库,奉献fixes也会变得更简单。 希望你已经决定把你的插件代码仓库公开出来。无论你采用了哪家的服务,_至少_代码库需要能够被人们获取。 ## 文档 你已经用Vim自己的帮助文档格式透彻地给插件作了文档。但你的工作还没完成呢。 你还需要写出一个简介,包括下面几条: 1. 你的插件可以用来干什么? 2. 为什么用户想要用它? 3. 为什么它比同类的插件(如果有的话)要好? 4. 它遵循什么协议? 5. 一个到完整文档的链接,可以考虑借助[vim-doc](http://vim-doc.heroku.com/)网站进行渲染。 这些应该放在你的README文件(它将会显示在Bitbucket或GitHub的版本库的主页面),你也可以把它作为Vim.org上的插件描述。 包括一些屏幕截图总是一个好主意。作为一个文本编辑器不意味着Vim没有一个用户界面。 ## 贴小广告 一旦你已经把插件部署到各个托管网站上,是时候向全世界宣传它的到来! 你可以在Twitter上向你的粉丝介绍,在Reddit的[/r/vim](http://reddit.com/r/vim/)版面推广它,在你的个人网站上写关于它的博客, 在[Vim邮件列表](http://www.vim.org/maillist.php)上给新手们派小广告。 无论何时,当你推出自己创作的东西,你总会收到一些赞美和批评。 不要对不好的评价耿耿于怀。倾听他们的呼声,同时厚着脸皮,心态平和地对待作品中被指出的小瑕缺(不管对还是不对)。 没有什么是十全十美的,而且这就是Internet,所以如果你想保持快乐和激情,你需要拿得起放得下。 ## 练习 如果你还没有Vim.org账户,创建一个。 察看你喜欢的插件的READEME文件,看看它们是怎么组织起来的以及它们包含的信息。
';

文档

最后更新于:2022-04-01 21:06:05

我们的Potion插件有着许多有用的功能,但是无人知晓这一点,除非我们留下了文档! Vim自身的文档非常棒。它不仅是详细地,而且也是非常透彻的。 同时,它也启发了许多插件作者写出很好的插件文档,结果是在Vimscript社区里营造出强大的文档文化。 ## 如何使用文档 当你阅读Vim里的`:help`条目时,你显然注意到了有些内容被高亮得跟别的不一样。 让我们看一下这是怎么回事。 打开任何帮助条目(比如`:help help`)并执行`:set filetype?`。Vim显示`filetype=help`。 现在执行`:set filetype=text`,你将看到高亮消失了。 再次执行`:set filetype=help`,高亮又回来了。 这表明Vim帮助文件只是获得了语法高亮的文本文件,一如其他的文件格式! 这意味着你可以照着写并获得同样的高亮。 在你的插件repo中创建名为`doc/potion.txt`文件。 Vim/Pathogen在索引帮助条目时查找在`doc`文件夹下的文件,所以我们在此写下插件的帮助文档。 用Vim打开这个文件并执行`:set filetype=help`,这样你在输入的时候就可以看到语法高亮。 ## 帮助的题头 帮助文件的格式是一个取决于个人品味的问题。 尽管这么说,我还是讲讲一种在当前的Vimscript社区逐渐被广泛使用的文档格式。 文件的第一行应该包括帮助文件的文件名,接下来是一行对插件功能的描述。 在你的`potion.txt`文件的第一行加上下面的内容: ~~~ *potion.txt* functionality for the potion programming language ~~~ 在帮助文件中用星号括起一个词创建了一个可以用于跳转的"tag"。 执行`:Helptags`来让Pathogen重新索引帮助tags,接着打开一个新的Vim窗口并执行`:help potion.txt`。 Vim将打开你的帮助文档,一如往日打开别人写的。 接下来你应该把你的插件的大名和一个老长老长的描述打上去。 有些作者(包括我)喜欢在这上面花点心思,用一些ASCII艺术字点缀一下。 在`potion.txt`文件内加上一个不错的标题节: ~~~ *potion.txt* functionality for the potion programming language ___ _ _ ~ / _ \___ | |_(_) ___ _ __ ~ / /_)/ _ \| __| |/ _ \| '_ \ ~ / ___/ (_) | |_| | (_) | | | | ~ \/ \___/ \__|_|\___/|_| |_| ~ Functionality for the Potion programming language. Includes syntax highlighting, code folding, and more! ~~~ 我是通过执行`figlet -f ogre "Potion"`命令来得到这些有趣的字符的。 [Figlet](http://www.figlet.org/)是一个好玩的小程序,可以生成ACSII艺术字。 每一行结尾的`~`字符保证Vim不会尝试高亮或隐藏艺术字中的某些字符。 ## 写什么文档 接下来通常是一个内容目录。不过,首先,让我们决定我们想要写的文档内容。 在给一个新插件写文档时,我通常从下面的列表开始: * Introduction * Usage * Mappings * Configuration * License * Bugs * Contributing * Changelog * Credits 如果这是一个大插件,需要一个"大纲",我将写一个介绍段落来概括它的主要功能。 否则我会跳过它继续下一段。 一个"usage"段落应该解释,大体上用户将怎样_使用_你的插件。如果他们需要通过映射进行交互,告诉他们。 如果映射数目不是很多,你可以简单地在这里列出来,否则你可能需要创建一个单独的"mappings"段落来一一列出。 "configuration"段落应该详尽列出用户可以自定义的变量,以及它们的功能和默认值。 "license"段落应该指出插件代码所用的协议,连同一个指向协议完整文本的URL。 不要把整份协议放入帮助文件 -- 大多数用户知道这些常用的协议是什么意思,这样做只会徒增混乱。 "bugs"段落应该尽可能短小。列出所有你已知却尚未解决的主要的bugs,并告知用户如何向你报告他们抓到的新bug。 如果你希望你的用户可以向项目奉献bug fixes和features,他们需要怎么做。 应该把pull request发到GitHub呢?还是Bitbucket?要用email寄补丁吗? 要选择其中的一个还是全都得要?一个"contributing"段落可以清楚地表明你想要接受代码的方式。 一个changlog也是值得包含在内的,这样当用户从版本X更新到版本Y时,他们立即可以看到什么改变了。 此外,我强烈推荐你为你的插件使用一个合理的版本号计划,比如[Semantic Versioning](http://semver.org/),并一直坚持。 你的用户会感谢你的。 最后,我喜欢加入一个"credits"段落来留下自己的大名,列出影响该插件创作的其他插件,感谢奉献者,等等。 这样我们就有一个成功的开始了。对于许多特殊的插件,你可能觉得需要不这么做,那也没问题。 你仅需追随下面几条规则: * 透彻明了 * 不要废话 * 带领你的用户漫步于你的插件,从一无所知到了如指掌。 ## 内容目录 既然我们已经了解了应该要有的段落,把下面内容加入到`potion.txt`文件中: ~~~ ==================================================================== CONTENTS *PotionContents* 1\. Usage ................ |PotionUsage| 2\. Mappings ............. |PotionMappings| 3\. License .............. |PotionLicense| 4\. Bugs ................. |PotionBugs| 5\. Contributing ......... |PotionContributing| 6\. Changelog ............ |PotionChangelog| 7\. Credits .............. |PotionCredits| ~~~ 有很多事情需要在这里提一下。 首先,`=`字符组成的那行将被高亮。你可以用这些行醒目地隔开你的帮助文档各部分。 你也可以使用`-`隔开子段落,如果想要的话。 `*PotionContents*`将创建另一个tag,这样用户可以输入`:help PotionContents`来直接跳到内容目录。 用`|`包起每一个词将创建一个跳转到tag的链接。 用户可以把他们的光标移到词上并按下``跳转到对应tag,就像他们输入`:help TheTag`一样。 他们也可以用鼠标点开。 Vim将隐藏`*`和`|`并高亮其间的内容,所以用户将会看到一个整洁漂亮的目录,以便于跳到感兴趣的地方。 ## 段落 你可以像这样创建一个段头: ~~~ ==================================================================== Section 1: Usage *PotionUsage* This plugin with automatically provide syntax highlighting for Potion files (files ending in .pn). It also... ~~~ 确保对`*`围起的内容创建了正确的tags,这样你的目录的链接才能正常工作。 继续并为目录中每一部分创建段头。 ## 例子 我可以讲述所有的帮助文件语法和怎样使用它们,但我不喜欢这样。 所以,不如我给你一系列不同类型的Vim插件文档作为例子。 对于每个例子,复制文档源代码到一个Vim缓冲区并设置它的filetype为`help`来观察它的渲染。 如果你想比较每个渲染效果,切回`text`看看。 你也许需要使用你的Vimscript技能为当前缓冲区创建一个切换于`help`和`text`的映射。 * [Clam](https://github.com/sjl/clam.vim/blob/master/doc/clam.txt),我自己用来写shell命令的插件。这是一个很小的范例,满足了我前面讲过的大多数内容。 * [NERD tree](https://github.com/scrooloose/nerdtree/blob/master/doc/NERD_tree.txt),Scrooloose写的一个文件浏览插件。 注意大体结构,还有他如何在详尽解释每一项之前,总结出一个易读的列表。 * [Surround](https://github.com/tpope/vim-surround/blob/master/doc/surround.txt),Tim Pope写的一个处理环绕字符的插件。 注意到它没有目录,以及不同的段头风格和表格列项(table column headers)。 弄懂他是怎么做到的,并想想你是否喜欢这种风格。这是个人风格问题啦。 * [Splice](https://github.com/sjl/splice.vim/blob/master/doc/splice.txt),我自己用来解决版本控制中[three-way merge conflict](http://en.wikipedia.org/wiki/Merge_(revision_control)#Three-way_merge)的插件。 注意映射列表的排版方式,以及我怎样使用ASCII流派的图片来解释布局。有时候,一图胜千言。 不要忘了,Vim本身的文档也可以作为一个例子。这会给你许多可供学习的内容。 ## 写! 既然你已经看过其他插件如何规划和撰写它们的文档,给你的Potion插件填补上文档内容吧。 如果你不熟悉技术文档的写作,这可能会是个挑战。 学习如何去写并不容易,但一如其他技能,它需要的是更多的练习,所以现在开始吧! 你不必苛求完美,从战争中学习战争即可。 不要惧于写你没有完全弄懂的事情,待会丢掉它重写即可。 经常只要在缓冲区中_信手留下几笔_,将会带动你的头脑进入写作状态。 任何时候你想重起炉灶,旧版本一直会在版本控制中等你。 一个开始的好方法是想象你身边也有一个使用Vim的好基友。 他对你的插件很感兴趣却未曾用过,而你的目标是让他熟练掌握。 在你写下插件工作的方式之前,考虑如何向人类进行介绍,可以让你脚踏实地,避免太深入于技术层面。 如果你依旧卡住了,感觉自己无力应对写一个完整插件的文档的挑战,尝试做点简单的。 在你的`~/.vimrc`中找一个映射并给它写下完整的文档。解释它是干什么的,怎么用它,它怎么工作。 比如,这是我的`~/.vimrc`的一个例子: ~~~ " "Uppercase word" mapping. " " This mapping allows you to press in insert mode to convert the " current word to uppercase. It's handy when you're writing names of " constants and don't want to use Capslock. " " To use it you type the name of the constant in lowercase. While " your cursor is at the end of the word, press to uppercase it, " and then continue happily on your way: " " cursor " v " max_connections_allowed| " " MAX_CONNECTIONS_ALLOWED| " ^ " cursor " " It works by exiting out of insert mode, recording the current cursor " location in the z mark, using gUiw to uppercase inside the current " word, moving back to the z mark, and entering insert mode again. " " Note that this will overwrite the contents of the z mark. I never " use it, but if you do you'll probably want to use another mark. inoremap mzgUiw`za ~~~ 它比一个完整插件的文档短很多,却是一个练习写作的好练习。 如果你把`~/.vimrc`放到Bitbucket或GitHub,别人也更容易理解。 ## 练习 给Potion插件每一部分写下文档。 阅读`:help help-writing`来帮助你写帮助文档。 阅读`:help :left`, `:help :right`,和`:help :center`来学习三个有用的命令使得你的ASCII艺术字更好看。
';

自动加载

最后更新于:2022-04-01 21:06:03

我们已经为我们的Potion插件写了大量的功能,覆盖了本书所要讲的内容。 在结束之前,我们将讲到一些非常重要的方法,可以给我们的插件锦上添花。 第一项是使用自动加载让我们的插件更有效率。 ## 如何自动加载 目前,当用户加载我们的插件时(比如打开了一个Potion文件),所有的功能都会被加载。 我们的插件还很小,所以这大概不是什么大问题,但对于较大的插件,加载全部代码将会导致可被察觉的卡顿。 Vim使用称为"自动加载(autoload)"来解决这个问题。自动加载让你直到需要时才加载某一部分代码。 会有一些性能上的损失,但如果用户不总是需要你的插件的每一行代码,自动加载将带来速度上的飞跃。 示范一下它是怎么工作的。看看下面的命令: ~~~ :call somefile#Hello() ~~~ 当你执行这个命令,Vim的行为与平常的函数调用有些许不同。 如果这个函数已经加载了,Vim简单地像平常一样调用它。 否则Vim将在你的`~/.vim`(或`~/.vim/bundles/对应的插件/autoload`)下查找一个叫做`autoload/somefile.vim`的文件。 如果文件存在,Vim将加载/source文件。接着Vim就会像平常一样调用它。 在这个文件内,函数应该这样定义: ~~~ function somefile#Hello() " ... endfunction ~~~ 你可以在函数名中使用多个`#`来表示子目录。举个例子: ~~~ :call myplugin#somefile#Hello() ~~~ 这将在`autoload/myplugin/somefile.vim`查找自动加载文件。 里面的函数需要使用自动加载的绝对路径进行定义: ~~~ function myplugin#somefile#Hello() " ... endfunction ~~~ ## 实验一下 为了更好地理解自动加载,让我们实验一下。 创建一个`~/.vim/autoload/example.vim`文件并加入下面的内容: ~~~ echom "Loading..." function! example#Hello() echom "Hello, world!" endfunction echom "Done loading." ~~~ 保存文件并执行`:call example#Hello()`。Vim将输出下面内容: ~~~ Loading... Done loading. Hello, world! ~~~ 这个小演示证明了几件事: 1. Vim的确是在半途加载了`example.vim`文件。当我们打开Vim的时候它并不存在,所以不可能是在启动时加载的。 2. 当Vim找到它需要自动加载的文件后,它在调用对应函数之前就加载了整个文件。 **先不要关闭Vim**,修改函数的定义成这样: ~~~ echom "Loading..." function! example#Hello() echom "Hello AGAIN, world!" endfunction echom "Done loading." ~~~ 保存文件并**不要关闭Vim**,执行`:call example#Hello()`。Vim将简单地输出: ~~~ Hello, world! ~~~ Vim已经有了`example#Hello`的一个定义,所以它不再需要重新加载文件,这意味着: 1. 函数以外的代码将不再执行。 2. 它不会反映函数本身的变化。 现在执行`:call example#BadFunction()`。你将再一次看到加载信息,伴随着一个函数不存在的错误。 但现在尝试再次执行`:call example#Hello()`。这次你将看到更新后的信息! 目前为止你应该清晰地了解到Vim会怎么处理一个自动加载类型的函数调用吧: 1. 它首先是否已经存在同名的函数了。如果是,就调用它。 2. 否则,查找名字对应的文件,并source它。 3. 然后试图调用那个函数。如果成功,太棒了。如果失败,就输出一个错误。 如果你还是没有完成弄懂,回到前面重新过一遍演示,注意观察每条规则生效的地方。 ## 自动加载什么 自动加载不是没有缺陷的。 设置了自动加载后,会有一些(小的)运行开销,更别说你不得不在你的代码里容忍丑陋的函数名了。 正因为如此,如果你不是写一个用户会在_每次_打开Vim对话时都用到的插件,最好尽量把功能代码都挪到autoload文件中去。 这将减少你的插件在用户启动Vim时的影响,尤其是在人们安装了越来越多的插件的今天。 所以有什么是可以安全地自动加载?那些不由你的用户直接调用的部分。 映射和自定义命令不能自动加载(因为它们需要由用户调用),但别的许多东西都可以。 让我们看看Potion插件里有什么可以自动加载的。 ## 在Potion插件里添加自动加载 我们将从编译和执行功能开始下手。 在前一章的最后,我们的`ftplugin/potion/running.vim`文件大概是这样: ~~~ if !exists("g:potion_command") let g:potion_command = "/Users/sjl/src/potion/potion" endif function! PotionCompileAndRunFile() silent !clear execute "!" . g:potion_command . " " . bufname("%") endfunction function! PotionShowBytecode() " Get the bytecode. let bytecode = system(g:potion_command . " -c -V " . bufname("%")) " Open a new split and set it up. vsplit __Potion_Bytecode__ normal! ggdG setlocal filetype=potionbytecode setlocal buftype=nofile " Insert the bytecode. call append(0, split(bytecode, '\v\n')) endfunction nnoremap r :call PotionCompileAndRunFile() nnoremap b :call PotionShowBytecode() ~~~ 这个文件仅仅当Potion文件加载时才会调用,所以它通常不会影响Vim的启动时间。 但可能会有一些用户就是不想要这些功能,所以如果我们可以自动加载某些部分, 每次打开Potion文件时可以省下他们以毫秒记的时间。 是的,这种情况下我们不会节省多少。 但你可以想象到可能有那么一个插件包括了数千行可以通过自动加载来减少每次的加载时间的代码。 让我们开始吧。在你的插件repo中创建一个`autoload/potion/running.vim`文件。 然后移动两个函数进去,并修改它们的名字,让它们看上去像: ~~~ echom "Autoloading..." function! potion#running#PotionCompileAndRunFile() silent !clear execute "!" . g:potion_command . " " . bufname("%") endfunction function! potion#running#PotionShowBytecode() " Get the bytecode. let bytecode = system(g:potion_command . " -c -V " . bufname("%")) " Open a new split and set it up. vsplit __Potion_Bytecode__ normal! ggdG setlocal filetype=potionbytecode setlocal buftype=nofile " Insert the bytecode. call append(0, split(bytecode, '\v\n')) endfunction ~~~ 注意`potion#running`部分的函数名怎么匹配它们所在的路径。 现在修改`ftplugin/potion/running.vim`文件成这样: ~~~ if !exists("g:potion_command") let g:potion_command = "/Users/sjl/src/potion/potion" endif nnoremap r \ :call potion#running#PotionCompileAndRunFile() nnoremap b \ :call potion#running#PotionShowBytecode() ~~~ 保存文件,关闭Vim,然后打开你的`factorial.pn`文件。尝试这些映射,确保它们依然正常工作。 确保你仅仅在第一次执行其中一个映射的时候才看到诊断信息`Autoloading...`(你可能需要使用`:message`来看到)。 一旦认为自动加载正常工作,你可以移除那些信息。 正如你看到的,我们保留`nnoremap`映射部分不变。 我们不能自动加载它们,不然用户就没办法引发自动加载了! 你将在Vim插件中普遍看到:大多数的功能将位于自动加载函数中,仅有`nnoremap`和`command`命令每次都被Vim加载。 每次你写一个有用的Vim插件时,不要忘了这一点。 ## 练习 阅读`:help autoload` 稍微测试一下并弄懂自动加载变量是怎么一回事。 假设你想要强制加载一个Vim已经加载的自动加载文件,并不会惊扰到用户。 你会怎么做?你可能想要阅读`:help silent!`(译注:此处应该是`:help :silent`)。不过在现实生活中请不要那么做。
';

外部命令

最后更新于:2022-04-01 21:06:00

Vim遵循UNIX哲学"做一件事,做好它"。 与其试图集成你可能想要的功能到编辑器自身,更好的办法是在适当时使用Vim来调用外部命令。 让我们在插件中添加一些跟Potion编译器交互的命令,来浅尝在Vim里面调用外部命令的方法。 ## 编译 首先我们将加入一个命令来编译和执行当前Potion文件。 有很多方法可以实现这一点,不过我们暂且用外部命令简单地实现。 在你的插件的repo中创建`potion/ftplugin/potion/running.vim`文件。 这将是我们创建编译和执行Potion文件的映射的地方。 ~~~ if !exists("g:potion_command") let g:potion_command = "potion" endif function! PotionCompileAndRunFile() silent !clear execute "!" . g:potion_command . " " . bufname("%") endfunction nnoremap r :call PotionCompileAndRunFile() ~~~ 第一部分以全局变量的形式储存着用来执行Potion代码的命令,如果还没有设置过的话。 我们之前见过类似的检查了。 如果`potion`不在用户的`$PATH`内,这将允许用户覆盖掉它, 比如在`~/.vimrc`添加类似`let g:potion_command = "/Users/sjl/src/potion/potion"`的一行。 最后一行添加了一个buffer-local的映射来调用我们前面定义的函数。 不要忘了,由于这个文件位于`ftdetect/potion`文件夹,每次一个文件的`filetype`设置成`potion`,它都会被执行。 真正实现了功能的地方在`PotionCompileAndRunFile()`。 保存文件,打开`factorial.pn`并按下`r`来执行这个映射,看看发生了什么。 如果`potion`位于你的`$PATH`下,代码会被执行,你应该在终端看到输出(或者在窗口底部,如果你用的是GUI vim)。 如果你看到了没有找到`potion`命令的错误,你需要像上面提到那样在`~/.vimrc`内设置`g:potion_command`。 让我们了解一下`PotionCompileAndRunFile()`的工作原理。 ## Bang! `:!`命令(念作"bang")会执行外部命令并在屏幕上显示它们的输出。尝试执行下面的命令: ~~~ :!ls ~~~ Vim将输出`ls`命令的结果,同时还有"请按 ENTER 或其它命令继续"的提示。 当这样执行时,Vim不会传递任何输入给外部命令。为了验证,执行: ~~~ :!cat ~~~ 打入一些行,然后你将看到`cat`命令把它们都吐回来了,就像你是在Vim之外执行`cat`。按下Ctrl-D来结束。 想要执行一个外部命令并避免`请按 ENTER 或其它命令继续`的提示,使用`:silent !`。执行下面的命令: ~~~ :silent !echo Hello, world. ~~~ 如果在GUI Vim比如MacVim或gVim下执行,你将不会看到`Hello,world.`的输出。 如果你在终端下执行,你看到的结果取决于你的配置。 一旦执行了一个`:silent !`,你可能需要执行`:redraw!`来重新刷新屏幕。 注意这个命令是`:silent !`而不是`:silent!`(看到空格了吗?)! 这是两个不一样的命令,我们想要的是前者!Vimscript奇妙吧? 让我们回到`PotionCompileAndRun()`上来: ~~~ function! PotionCompileAndRunFile() silent !clear execute "!" . g:potion_command . " " . bufname("%") endfunction ~~~ 首先我们执行一个`silent !clear`命令,来清空屏幕输出并避免产生提示。 这将确保我们仅仅看到本次命令的输出,如果一再执行同样的命令,你会觉得有用的。 在下一行我们使用老朋友`execute`来动态创建一个命令。建立的命令看上去类似于: ~~~ !potion factorial.pn ~~~ 注意这里没有`silent`,所以用户将看到命令输出,并不得不按下enter来返回Vim。 这就是我们想要的,所以就这样设置好了。 ## 显示字节码 Potion编译器有一个显示由它生成的字节码的选项。如果你正试图在非常低级的层次下debug,这将帮上忙。 在shell里执行下面的命令: ~~~ potion -c -V factorial.pn ~~~ 你将看到一大堆像这样的输出: ~~~ -- parsed -- code ... -- compiled -- ; function definition: 0x109d6e9c8 ; 108 bytes ; () 3 registers .local factorial ; 0 .local print_line ; 1 .local print_factorial ; 2 ... [ 2] move 1 0 [ 3] loadk 0 0 ; string [ 4] bind 0 1 [ 5] loadpn 2 0 ; nil [ 6] call 0 2 ... ~~~ 让我们添加一个使用户可以在新的Vim分割下,看到当前Potion代码生成的字节码的映射, 这样他们能更方便地浏览并测试输出。 首先,在`ftplugin/potion/running.vim`底部添加下面一行: ~~~ nnoremap b :call PotionShowBytecode() ~~~ 这里没有什么特别的 -- 只是一个简单的映射。现在先描划出函数的大概框架: ~~~ function! PotionShowBytecode() " Get the bytecode. " Open a new split and set it up. " Insert the bytecode. endfunction ~~~ 既然已经建立起一个框架,让我们把它变成现实吧。 ## system() 有许多不同的方法可以实现这一点,所以我选择相对便捷的一个。 执行下面的命令: ~~~ :echom system("ls") ~~~ 你应该在屏幕的底部看到`ls`命令的输出。如果执行`:message`,你也能看到它们。 Vim函数`system()`接受一个字符串命令作为参数并以字符串形式返回那个命令的输出。 你可以把另一个字符串作为参数传递给`system()`。执行下面命令: ~~~ :echom system("wc -c", "abcdefg") ~~~ Vim将显示`7`(以及一些别的)。 如果你像这样传递第二个参数,Vim将写入它到临时文件中并通过管道作为标准输入输入到命令里。 目前我们不需要这个特性,不过它值得了解。 回到我们的函数。编辑`PotionShowBytecode()`来填充框架的第一部分: ~~~ function! PotionShowBytecode() " Get the bytecode. let bytecode = system(g:potion_command . " -c -V " . bufname("%")) echom bytecode " Open a new split and set it up. " Insert the bytecode. endfunction ~~~ 保存文件,在`factorial.pn`处执行`:set ft=potion`重新加载,并使用`b`尝试一下。 Vim会在屏幕的底部显示字节码。一旦看到它成功执行了,你可以移除`echom`。 ## 在分割上打草稿 接下来我们将打开一个新的分割把结果展示给用户。 这将让用户能够借助Vim的全部功能来浏览字节码,而不是仅仅只在屏幕上昙花一现。 为此我们将创建一个"草稿"分割:一个分割,它包括一个永不保存并每次执行映射都会被覆盖的缓冲区。 把`PotionShowBytecode()`函数改成这样: ~~~ function! PotionShowBytecode() " Get the bytecode. let bytecode = system(g:potion_command . " -c -V " . bufname("%")) " Open a new split and set it up. vsplit __Potion_Bytecode__ normal! ggdG setlocal filetype=potionbytecode setlocal buftype=nofile " Insert the bytecode. endfunction ~~~ 新增的命令应该很好理解。 `vsplit`创建了名为`__Potion_Bytecode__`的新竖直分割。 我们用下划线包起名字,使得用户注意到这不是普通的文件(它只是显示输出的缓冲区)。 下划线不是什么特殊用法,只是约定俗成罢了。 接着我们用`normal! ggdG`删除缓冲区中的所有东西。 第一次执行这个映射时,并不需要这样做,但之后我们将重用`__Potion_Bytecode__`缓冲区,所以需要清空它。 接下来我们为这个缓冲区设置两个本地设置。首先我们设置它的文件类型为`potionbytecode`,只是为了指明它的用途。 我们也改变`buftype`为`nofile`,告诉Vim这个缓冲区与磁盘上的文件不相关,这样它就不会把缓冲区写入。 最后还剩下把我们保存在`bytecode`变量的字节码转储进缓冲区。完成函数,让它看上去像这样: ~~~ function! PotionShowBytecode() " Get the bytecode. let bytecode = system(g:potion_command . " -c -V " . bufname("%") . " 2>&1") " Open a new split and set it up. vsplit __Potion_Bytecode__ normal! ggdG setlocal filetype=potionbytecode setlocal buftype=nofile " Insert the bytecode. call append(0, split(bytecode, '\v\n')) endfunction ~~~ Vim函数`append()`接受两个参数:一个将被附加内容的行号和一个将按行附加的字符串列表。 举个例子,尝试执行下面命令: ~~~ :call append(3, ["foo", "bar"]) ~~~ 这将附加两行,`foo`和`bar`,在你当前缓冲区的第三行之后。 这次我们将在表示文件开头的第0行之后添加。 我们需要一个字符串列表来附加,但我们只有来自`system()`调用的单个包括换行符的字符串。 我们使用Vim的`split()`函数来分割这一大坨文本成一个字符串列表。 `split()`接受一个待分割的字符串和一个查找分割点的正则表达式。这真的很简单。 现在函数已经完成了,试一下对应的映射。 当你在`factorial.pn`中执行`b`,Vim将打开新的包括Potion字节码的缓冲区。 修改Potion源代码,保存文件,执行映射来看看会有什么不同的结果。 ## 练习 阅读`:help bufname`。 阅读`:help buftype`。 阅读`:help append()`。 阅读`:help split()`。 阅读`:help :!`。 阅读`:help :read`和`:help :read!`(我们没有讲到这些命令,不过它们非常好用)。 阅读`:help system()`。 阅读`:help design-not`。 目前,我们的插件要求用户在执行映射之前手动保存文件来使得他们的改变起效。 当今撤销已经变得非常轻易,所以修改写过的函数来自动替他们保存。 如果你在一个带语法错误的Potion文件上执行这个字节码映射,会发生什么?为什么? 修改`PotionShowBytecode()`函数来探测Potion编译器是否返回一个错误,并向用户输出错误信息。 ## 加分题 每次你执行字节码映射时,一个新的竖直分割都会被创建,即使用户没有关闭上一个。 如果用户没有一再关闭这些窗口,他们最终将被大量额外的窗口困住。 修改`PotionShowBytecode()`来探测`__Potion_Bytecode__`缓冲区的窗口是否已经打开了, 如果是,切换到它上去而不是创建新的分割。 你大概想要阅读`:help bufwinnr()`来获取帮助。 ## 额外的加分题 还记得我们设置临时缓冲区的`filetype`为`potionbytecode`? 创建`syntax/potionbytecode.vim`文件并为Potion字节码定义语法高亮,使得它们更易读。
';

Potion段移动

最后更新于:2022-04-01 21:05:58

既然知道了段移动的工作原理,让我们重新映射这些命令来使得它们对于Potion文件起作用。 首先我们要决定Potion文件中"段"的意义。 有两对段移动命令,所以我们可以总结出两套组合,我们的用户可以选择自己喜欢的一个。 让我们使用下面两个组合来决定哪里是Potion中的段: 1. 任何在空行之后的,第一个字符为非空字符的行,以及文件首行。 2. 任何第一个字符为非空字符,包括一个等于号,并以冒号结尾的行。 稍微拓展我们的`factorial.pn`例子,这就是那些规则当作段头的地方: ~~~ # factorial.pn 1 # Print some factorials, just for fun. factorial = (n): 1 2 total = 1 n to 1 (i): total *= i. total. print_line = (): 1 2 "-=-=-=-=-=-=-=-\n" print. print_factorial = (i): 1 2 i string print '! is: ' print factorial (i) string print "\n" print. "Here are some factorials:\n\n" print 1 print_line () 1 10 times (i): print_factorial (i). print_line () ~~~ 我们的第一个定义更加自由。它定义一个段为一个"顶级的文本块"。 第二个定义则严格一点。它定义一个段为一个函数定义。 ## 自定义映射 在你的插件的repo中创建`ftplugin/potion/sections.vim`。 这将是我们放置段移动代码的地方。记得一旦一个缓冲区的`filetype`设置为`potion`,这里的代码就会执行。 我们将重新映射全部四个段移动命令,所以继续并创建一个骨架: ~~~ noremap ';

段移动原理

最后更新于:2022-04-01 21:05:56

如果你未曾用过Vim的段移动命令 (`[[`, `]]`, `[]` and `][`),现在花上几秒读读它们的帮助文档。 也顺便读读`:help section`。 还是不懂?这不是什么问题,我第一次读这些的时候也是这样。 在写代码之前,我们先岔开来学习这些移动是怎么工作的,然后在下一章我们将使得我们的Potion插件支持它们。 ## Nroff文件 四个"段移动"命令正如其字面上的含义,可以用来在文件的"段"之间移动。 这些命令默认为[nroff文件][]而设计。 Nroff类似于LaTex或Markdown -- 它是用来写标记文本的(最终会生成UNIX man页面)。 Nroff文件使用一组"macro"来定义"段头"。 比如,这里有个来自于`awk`man页面的例子: ~~~ .SH NAME *** awk \- pattern-directed scanning and processing language .SH SYNOPSIS *** .B awk [ .BI \-F .I fs ] [ .BI \-v .I var=value ] [ .I 'prog' | .BI \-f .I progfile ] [ .I file ... ] .SH DESCRIPTION *** .I Awk scans each input .I file for lines that match ... ~~~ 以`.SH`开头的行就是段头。我用`***`把它们标记出来。 四个段移动命令将在段头行之间移动你的光标。 Vim以`.`和nroff的段头符开始的任何行当做一个段头,_即使你编辑的不是nroff文件_! 你可以改变`sections`设置来改变段头符,但Vim依旧需要在行开头有一个点,而且段头符必须是成对的字符, 所以这样改对Potion文件不会有足够的灵活性。 ## 括号 段移动命令_也_查看另一样东西:一个打开或关闭的大括号(`{`或`}`)作为行的第一个字符。 `[[`和`]]`查看开括号,而`[]`和`][`查看闭括号。 这额外的"行为"使得你可以在C风格语言的段之间轻松移动。 然而,这些规则也依旧没有顾及你正在编辑的文件类型! 加入下面内容到一个缓冲区里: ~~~ Test A B Test .SH Hello A B Test { A Test } B Test .H World A B Test Test A B ~~~ 现在执行`:set filetype=basic`来告诉Vim这是一个BASIC文件,并尝试段移动命令。 `[[`和`]]`命令将在标记为`A`的行之间移动,而`[]`和`][`将在标记为`B`的行之间移动。 这告诉我们,Vim总是用同样的两条规则来处理段移动,即使没有一条是起作用的(比如在BASIC中的情况)! ## 练习 再次阅读`:help section`,现在你应该可以理解段移动了。 也顺便读读`:help sections`吧。
';

高级折叠

最后更新于:2022-04-01 21:05:53

在上一章里我们用Vim的`indent`折叠方式,在Potion文件中增加了一些快捷而肮脏的折叠。 打开`factorial.pn`并用`zM`关闭所有的折叠。文件现在看起来就像这样: ~~~ factorial = (n): +-- 5 lines: total = 1 10 times (i): +-- 4 lines: i string print ~~~ 展开第一个折叠,它看上去会是这样: ~~~ factorial = (n): total = 1 n to 1 (i): +--- 2 lines: # Multiply the running total. total. 10 times (i): +-- 4 lines: i string print ~~~ 这真不错,但我个人喜欢依照内容来折叠每个块的第一行。 在本章中我们将写下一些自定义的折叠代码,并在最后实现这样的效果: ~~~ factorial = (n): total = 1 +--- 3 lines: n to 1 (i): total. +-- 5 lines: 10 times (i): ~~~ 这将更为紧凑,而且(对我来说)更容易阅读。 如果你更喜欢`indent`也不是不行,不过最好学习本章来对Vim中实现折叠的代码的更深入的了解。 ## 折叠原理 为了写好自定义的折叠,我们需要了解Vim对待("thinks")折叠的方式。简明扼要地讲解下规则: * 文件中的每行代码都有一个"foldlevel"。它不是为零就是一个正整数。 * foldlevel为零的行_不会_被折叠。 * 有同等级的相邻行会被折叠到一起。 * 如果一个等级X的折叠被关闭了,任何在里面的、foldlevel不小于X的行都会一起被折叠,直到有一行的等级小于X。 通过一个例子,我们可以加深理解。打开一个Vim窗口然后粘贴下面的文本进去。 ~~~ a b c d e f g ~~~ 执行下面的命令来设置`indent`折叠: ~~~ :setlocal foldmethod=indent ~~~ 花上一分钟玩一下折叠,观察它是怎么工作的。 现在执行下面的命令来看看第一行的foldlevel: ~~~ :echom foldlevel(1) ~~~ Vim显示`0`。现在看看第二行的: ~~~ :echom foldlevel(2) ~~~ Vim显示`1`。试一下第三行: ~~~ :echom foldlevel(3) ~~~ Vim再次显示`1`。这意味着第2,3行都属于一个level1的折叠。 这是每一行的foldlevel: ~~~ a 0 b 1 c 1 d 2 e 2 f 1 g 0 ~~~ 重读这一部分开头的几条规则。打开或关闭每个折叠,观察foldlevel,并确保你理解了为什么会这样折叠。 一旦你已经自信地认为你理解了每行的foldlevel是怎么影响折叠结构的,继续看下一部分。 ## 首先:做一个规划 在我们埋头敲键盘之前,先为我们的折叠功能规划出几条大概的规则。 首先,同等缩进的行应该要折叠到一块。我们也希望_上_一行也一并折叠,达到这样的效果: ~~~ hello = (name): 'Hello, ' print name print. ~~~ 将折叠成这样: ~~~ +-- 3 lines: hello = (name): ~~~ 空行应该算入_下_一行,因此折叠底部的空行不会包括进去。这意味着类似这样的内容: ~~~ hello = (name): 'Hello, ' print name print. hello('Steve') ~~~ 将折叠成这样: ~~~ +-- 3 lines: hello = (): hello('Steve') ~~~ 而_不是_这样: ~~~ +-- 4 lines: hello = (): hello('Steve') ~~~ 这当然是属于个人偏好的问题,但现在我们就这么定了。 ## 开始 现在开始写我们的自定义折叠代码吧。 打开Vim,分出两个分割,一个是`ftplugin/potion/folding.vim`,另一个是示例代码`factorial.pn`。 在上一章我们关闭并重新打开Vim来使得`folding.vim`生效,但其实还有更简单的方法。 不要忘记每当设置一个缓冲区的`filetype`为`potion`的时候,在`ftplugin/potion/`下的所有文件都会被执行。 这意味着仅需在`factorial.pn`的分割下执行`:set ft=potion`,Vim将重新加载折叠代码! 这比每次都关闭并重新打开文件要快多了。 唯一需要铭记的是,你得_保存_`folding.vim`到硬盘上,否则未保存的改变不会起作用。 ## Expr折叠 为了获取折叠上的无限自由,我们将使用Vim的`expr`折叠。 我们可以继续并从`folding.vim`移除`foldignore`,因为它只在使用`indent`的时候生效。 我们也打算让Vim使用`expr`折叠,所以把`folding.vim`改成这样: ~~~ setlocal foldmethod=expr setlocal foldexpr=GetPotionFold(v:lnum) function! GetPotionFold(lnum) return '0' endfunction ~~~ 第一行只是告诉Vim使用`expr`折叠。 第二行定义了Vim用来计算每一行的foldlevel的表达式。 当Vim执行某个表达式,它会设置`v:lnum`为它需要的对应行的行号。 我们的表达式将把这个数字作为自定义函数的参数。 最后我们定义一个对任意行均返回`0`的占位(dummy)函数。 注意它返回的是一个字符串而不是一个整数。等会我们就知道为什么这么做。 继续并重新加载折叠代码(保存`folding.vim`并对`factorial.pn`执行`:set ft=potion`)。 我们的函数对任意行均返回`0`,所以Vim将不会进行任何折叠。 ## 空行 让我们先解决空行的特殊情况。修改`GetPotionFold`函数成这样: ~~~ function! GetPotionFold(lnum) if getline(a:lnum) =~? '\v^\s*$' return '-1' endif return '0' endfunction ~~~ 我们增加了一个`if`语句来处理空行。它是怎么起效的? 首先,我们使用`getline(a:lnum)`来以字符串形式获取当前行的内容。 我们把结果跟正则表达式`\v^\s*$`比较。记得`\v`表示"very magic"(我的意思是,正常的)模式。 这个正则表达式将匹配"行的开头,任何空白字符,行的结尾"。 比较是用大小写不敏感比较符`=~?`完成的。 技术上我们不用担心大小写,毕竟我们只匹配空白,但是我偏好在比较字符串时使用更清晰的方式。 如果你喜欢,可以使用`=~`代替。 如果需要唤起Vim中的正则表达式的回忆,你应该回头重读"基本正则表达式"和"Grep Operator"这两部分。 如果当前行包括一些非空白字符,它将不会匹配,我们将如前返回`0`。 如果当前行_匹配_正则表达式(i.e. 比如它是空的或者只有空格),就返回字符串`'-1'`。 之前我说过一行的foldlevel可以为0或者正整数,所以这会发生什么? ## 特殊折叠 你自定义的表达式可以直接返回一个foldlevel,或者返回一个"特殊字符串"来告诉Vim如何折叠这一行。 `'-1'`正是其中一种特殊字符串。它告知Vim,这一行的foldlevel为"undefined"。 Vim将把它理解为"该行的foldlevel等于其上一行或下一行的较小的那个foldlevel"。 这不是我们计划中的_最终_结果,但我们可以看到,它已经足够接近了,而且必将达到我们的目标。 Vim可以把undefined的行串在一起,所以假设你有三个undefined的行和接下来的一个level1的行, 它将设置最后一行为1,接着是倒数第二行为1,然后是第一行为1。 在写自定义的折叠代码时,你经常会发现有几种行你可以容易地设置好它们的foldlevel。 然后你就可以使用`'-1'`(或我们等会会看到的其他特殊foldlevel)来"瀑布般地"设置好剩余的行的foldlevel。 如果你重新加载了`factorial.pn`的折叠代码,Vim_依然_不会折叠任何行。 这是因为所有的行的foldlevel要不是为0,就是为"undefined"。 等级为0的行将影响undefined的行,最终导致所有的行的foldlevel都是`0`。 ## 缩进等级辅助函数 为了处理非空行,我们需要知道它们的缩进等级,所以让我们来创建一个辅助函数替我们计算它。 在`GetPotionFold`之上加上下面的函数: ~~~ function! IndentLevel(lnum) return indent(a:lnum) / &shiftwidth endfunction ~~~ 重新加载折叠代码。在`factorial.pn`缓冲区执行下面的命令来测试你的函数: ~~~ :echom IndentLevel(1) ~~~ Vim显示`0`,因为第一行没有缩进。现在在第二行试试看: ~~~ :echom IndentLevel(2) ~~~ 这次Vim显示`1`。第二行开头有四个空格,而`shiftwidth`设置为4,所以4除以4得1。 我们用它除以缓冲区的`shiftwidth`来得到缩进等级。 为什么我们使用`&shiftwidth`而不是直接除以4? 如果有人偏好使用2个空格缩进他们的Potion代码,除以4将导致不正确的结果。 使用`shiftwidth`可以允许任何缩进的空格数。 ## 再来一个辅助函数 下一步的方向尚未明朗。让我们停下来想想为了确定折叠非空行,还需要什么信息。 我们需要知道每一行的缩进等级。我们已经通过`IndentLevel`函数得到了,所以这个条件已经满足了。 我们也需要知道_下一个非空行_的缩进等级,因为我们希望折叠段头行到对应的缩进段中去。 让我们写一个辅助函数来得到给定行的下一个非空行的foldlevel。在`IndentLevel`上面加入下面的函数: ~~~ function! NextNonBlankLine(lnum) let numlines = line('$') let current = a:lnum + 1 while current <= numlines if getline(current) =~? '\v\S' return current endif let current += 1 endwhile return -2 endfunction ~~~ 这个函数有点长,不过很简单。让我们逐个部分分析它。 首先我们用`line('$')`得到文件的总行数。查查文档来了解`line()`。 接着我们设变量`current`为下一行的行号。 然后我们开始一个会遍历文件中每一行的循环。 如果某一行匹配正则表达式`\v\S`,表示匹配"有一个_非_空白字符",它就是非空行,所以返回它的行号。 如果某一行不匹配,我们就循环到下一行。 如果循环到达文件尾行而没有任何返回,这就说明当前行之后_没有_非空行! 我们返回`-2`来指明这种情况。`-2`不是一个有效的行号,所以用来简单地表示"抱歉,没有有效的结果"。 我们可以返回`-1`,因为它也是一个无效的行号。 我甚至可以选择`0`,因为Vim中的行号从`1`开始! 所以为何我选择`-2`这个看上去奇怪的选项? 我选择`-2`是因为我们正处理着折叠代码,而`'-1'`(和`'0'`)是特殊的Vim foldlevel字符串。 当眼睛正扫过代码时,看到`-1`,脑子里会立刻浮现起"undefined foldlevel"。 这对于`0`也差不多。 我在这里选择`-2`,就是为了突出它_不是_foldlevel,而是表示一个"错误"。 如果你觉得这不可理喻,你可以安心地替换`-2`为`-1`或`0`。 这只是代码风格问题。 ## 完成折叠函数 本章已经显得比较冗长了,所以现在把折叠函数包装起来(wrap up)吧。把`GetPotionFold`修改成这样: ~~~ function! GetPotionFold(lnum) if getline(a:lnum) =~? '\v^\s*$' return '-1' endif let this_indent = IndentLevel(a:lnum) let next_indent = IndentLevel(NextNonBlankLine(a:lnum)) if next_indent == this_indent return this_indent elseif next_indent < this_indent return this_indent elseif next_indent > this_indent return '>' . next_indent endif endfunction ~~~ 这里的新代码真多!让我们分开一步步来看。 ### 空行 首先我们检查空行。这里没有改动。 如果不是空行,我们就准备好处理非空行的情况了。 ### 获取缩进等级 接下来我们使用两个辅助函数来获取当前行和下一个非空行的折叠等级。 你可能会疑惑万一`NextNonBlankLine`返回错误码`-2`该怎么办。 如果这发生了,`indent(-2)`还会继续工作。对一个不存在的行号执行`indent()`将返回`-1`。 你可以试试`:echom indent(-2)`看看。 `-1`除以任意大于1的`shiftwidth`将返回`0`。 这好像有问题,不过它实际上不会有。现在暂时不用纠结于此。 ### 同级缩进 既然我们已经得到了当前行和下一非空行的缩进等级,我们可以比较它们并决定如何折叠当前行。 这里又是一个`if`语句: ~~~ if next_indent == this_indent return this_indent elseif next_indent < this_indent return this_indent elseif next_indent > this_indent return '>' . next_indent endif ~~~ 首先我们检查这两行是否有同样的缩进等级。如果相等,我们就直接把缩进等级当作foldlevel返回! 举个例子: ~~~ a b c d e ~~~ 假设我们正处理包含`c`的那一行,它的缩进等级为1。 下一个非空行("d")的缩进等级也是一样的,所以返回`1`作为foldlevel。 假设我们正处理"a",它的缩进等级为0。这跟下一非空行("b")的等级是一样的,所以返回`0`作为foldlevel。 在这个简单的示例中,可以分出两个foldlevel。 ~~~ a 0 b ? c 1 d ? e ? ~~~ 纯粹出于运气,这种情况也处理了在最后一行对特殊的"error"情况。 记得我们说过,如果我们的辅助函数返回`-2`,`next_indent`将会是`0`。 在这个例子中,行"e"的缩进等级为`0`,而`next_indent`也被设为`0`,所以匹配这种情况并返回`0`。 现在foldlevels是这样: ~~~ a 0 b ? c 1 d ? e 0 ~~~ ### 更低的缩进等级 我们再来看看那个`if`语句: ~~~ if next_indent == this_indent return this_indent elseif next_indent < this_indent return this_indent elseif next_indent > this_indent return '>' . next_indent endif ~~~ `if`的第二部分检查下一行的缩进等级是否比当前行_小_。就像是例子中行"d"的情况。 如果符合,将再一次返回当前行的缩进等级。 现在我们的例子看起来像这样: ~~~ a 0 b ? c 1 d 1 e 0 ~~~ 当然,你可以用`||`把两种情况连接起来,但是我偏好分开来写以显得更清晰。 你的想法可能不同。这只是风格问题。 又一次,纯粹出于运气,这种情况处理了其他来自辅助函数的"error"状态。设想我们有一个文件像这样: ~~~ a b c ~~~ 第一种情况处理行"b": ~~~ a ? b 1 c ? ~~~ 行"c"为最后一行,有着缩进等级1。由于我们的辅助函数,`next_indent`将设为`0`。 这匹配`if`语句的第二部分,所以foldlevel设为当前缩进等级,也即是`1`。 ~~~ a ? b 1 c 1 ~~~ 结果如我们所愿,"b"和"c"折叠到一块去了。 ### 更高的缩进等级 现在还剩下最后一个`if`语句: ~~~ if next_indent == this_indent return this_indent elseif next_indent < this_indent return this_indent elseif next_indent > this_indent return '>' . next_indent endif ~~~ 而我们的例子现在是: ~~~ a 0 b ? c 1 d 1 e 0 ~~~ 只剩下行"b"我们还不知道它的foldlevel,因为: * "b"的缩进等级为`0`。 * "c"的缩进等级为`1`。 * 1既不等于0,又不小于0。 最后一种情况检查下一行的缩进等级是否_大于_当前行。 这种情况下Vim的`indent`折叠并不理想,也是为什么我们一开始打算写自定义的折叠代码的原因! 最后的情况表示,当下一行的缩进比当前行多,它将返回一个以`>`开头和_下一行_的缩进等级构成的字符串。 这是什么意思呢? 从折叠表达式中返回的,类似`>1`的字符串表示Vim的特殊foldlevel中的一种。 它告诉Vim当前行需要_展开_一个给定level的折叠。 在这个简单的例子中,我们可以简单返回表示缩进等级的数字,但我们很快将看到为什么要这么做。 这种情况下"b"将展开level1的折叠,使我们的例子变成这样: ~~~ a 0 b >1 c 1 d 1 e 0 ~~~ 这就是我们想要的!万岁! ## 复习 如果你一步步做到了这里,你应该为自己感到骄傲。即使像这样的简单折叠代码,也会是令人绞尽脑汁的。 在我们结束之前,让我们重温最初的`factorial.pn`代码,看看我们的折叠表达式是怎么处理每一行的foldlevel的。 重新把`factorial.pn`代码列在这里: ~~~ factorial = (n): total = 1 n to 1 (i): # Multiply the running total. total *= i. total. 10 times (i): i string print '! is: ' print factorial (i) string print "\n" print. ~~~ 首先,所有的空行的foldlevel都将设为undefined: ~~~ factorial = (n): total = 1 n to 1 (i): # Multiply the running total. total *= i. total. undefined 10 times (i): i string print '! is: ' print factorial (i) string print "\n" print. ~~~ 所有折叠等级跟下一行的_相等_的行,它们的foldlevel等于折叠等级: ~~~ factorial = (n): total = 1 1 n to 1 (i): # Multiply the running total. 2 total *= i. total. undefined 10 times (i): i string print 1 '! is: ' print 1 factorial (i) string print 1 "\n" print. ~~~ 在下一行的缩进比当前行_更少_的情况下,也是同样的处理: ~~~ factorial = (n): total = 1 1 n to 1 (i): # Multiply the running total. 2 total *= i. 2 total. 1 undefined 10 times (i): i string print 1 '! is: ' print 1 factorial (i) string print 1 "\n" print. 1 ~~~ 最后的情况是下一行的缩进比当前行更多。如果这样,那就设当前行的折叠等级为展开下一行的折叠: ~~~ factorial = (n): >1 total = 1 1 n to 1 (i): >2 # Multiply the running total. 2 total *= i. 2 total. 1 undefined 10 times (i): >1 i string print 1 '! is: ' print 1 factorial (i) string print 1 "\n" print. 1 ~~~ 现在我们已经得到了文件中每一行的foldlevel。剩下的就是由Vim来解决未定义(undefined)的行。 不久前我说过undefined的行将选择相邻行中较小的那个foldlevel。 Vim手册是这么讲的,但不是十分地确切。 如果真是这样的,我们的文件中的空行的foldlevel为1,因为它相邻两行的foldlevel都为1。 事实上,空行的foldlevel将被设定成0! 这就是为什么我们不直接设置`10 times(i):`的foldlevel为1。我们告诉Vim该行_展开_一个level1的折叠。 Vim能够意识到这意味着undefined的行应该设置成`0`而不是`1`。 这样做背后的理由也许深埋在Vim的源码里。 通常Vim在处理undefined行时,对待特殊的foldlevel的行为都是很聪明的,所以你总能如愿以偿。 一旦Vim处理完undefined行,它会得到一个对每一行的折叠情况的完整描述,看上去像这样: ~~~ factorial = (n): 1 total = 1 1 n to 1 (i): 2 # Multiply the running total. 2 total *= i. 2 total. 1 0 10 times (i): 1 i string print 1 '! is: ' print 1 factorial (i) string print 1 "\n" print. 1 ~~~ 这就是了,我们完成啦!重新加载折叠代码,在`factorial.pn`中玩玩我们神奇的折叠功能吧! ## 练习 阅读`:help foldexpr`. 阅读`:help fold-expr`。注意你的表达式可以返回的所有特殊字符串。 阅读`:help getline`。 阅读`:help indent()`。 阅读`:help line()`。 想想为什么我们用`.`连接`>`和我们折叠函数给出的数字。如果我们使用的是`+`会怎样? 我们在全局空间中定义了辅助函数,但这不是好的做法。把它改到脚本本地的命名空间中。 放下本书,出去玩一下,让你的大脑从本章中清醒清醒。
';

基本折叠

最后更新于:2022-04-01 21:05:51

如果从未在Vim里使用过代码折叠,你不知道你都错过了什么。 阅读`:help usr_28`并花费时间在日常工作中使用它。 一旦到了铭记于指的程度,你就可以继续本章了。 ## 折叠类型 Vim支持六种不同的决定如何折叠你的文本的折叠类型。 ### Manual 你手动创建折叠并且折叠将被Vim储存在内存中。 当你关闭Vim时,它们也将一并烟消云散,而下次你编辑文件时将不得不重新创建。 在你把它跟一些自定义的创建折叠的映射结合起来时,这种方式会很方便。 在本书中,我们不会这么做,但当你想这么做的时候,它会帮上忙。 ### Marker Vim基于特定的字符组合折叠你的代码。 这些字符通常放置于注释中(比如`// {{{`), 不过在有些语言里,你可以使用该语言自己的语法代替,比如javascript的`{`和`}`。 纯粹为了你的编辑器,用注释割裂你的代码看上去有点丑,但好处是你可以定制特定的折叠。 如果你想以特定的方式组织一个大文件,这个类型将是非常棒的选择。 ### Diff 在diff文件时使用该特定的折叠类型。我们不会讨论它,因为Vim会自动使用它。 ### Expr 这让你可以用自定义的Vimscript来决定折叠的位置。它是最为强大的方式,不过也需要最繁重的工作。 下一章我们将讲到它。 ### Indent Vim使用你的代码的缩进来折叠。同样缩进等级的代码折叠到一块,空行则被折叠到周围的行一起去。 这是最便捷的方式,因为你的代码已经缩进过了;你仅仅需要启动它。 这将是我们用来折叠Potion代码的第一种方式。 ## Potion折叠 让我们再一次看一下Potion实例代码: ~~~ factorial = (n): total = 1 n to 1 (i): total *= i. total. 10 times (i): i string print '! is: ' print factorial (i) string print "\n" print. ~~~ 函数体和循环体已经缩进好了。这意味着我们可以不怎么费力就能实现一些基本的缩进。 在我们开始之前,在`total *= i`上添加一个注释,这样我们就有一个供测试的多行内部块。 你将在做练习的时候学到为什么我们需要这么做,但暂时先信任我。现在文件看上去就像这样: ~~~ factorial = (n): total = 1 n to 1 (i): # Multiply the running total. total *= i. total. 10 times (i): i string print '! is: ' print factorial (i) string print "\n" print. ~~~ 在你的Potion插件的版本库下创建一个`ftplugin`文件夹,然后在里面创建一个`potion`文件夹。 最后,在_`potion`文件夹_里面创建一个`folding.vim`文件。 不要忘了每次Vim设置一个buffer的`filetype`为`potion`时,它都会执行这个文件中的代码。 (因为它位于一个叫`potion`的文件夹) 将所有的折叠相关的代码放在同一个文件显然是一个好主意,它能帮我们维护我们的插件的繁多的功能。 在这个文件中加入下面一行: ~~~ setlocal foldmethod=indent ~~~ 关闭Vim,重新打开`factoria.pn`。用`zR`,`zM`和`za`尝试折叠功能。 一行Vimscript代码就能带来一些有用的折叠!这真是太酷了! 你可能注意到`factorial`函数的内循环里面的那几行不能折叠,尽管它们缩进了。 为什么会这样? 事实上,在使用`indent`折叠时,Vim默认忽略以`#`字符开头的行。 这在编辑C文件时很有用(这时`#`表示一个预编译指令),但在编辑其他文件时不怎么有意义。 让我们在`ftplugin/potion/folding.vim`中添加多一行来修复问题: ~~~ setlocal foldmethod=indent setlocal foldignore= ~~~ 关闭并重新打开`factorial.pn`,现在内部块可以正常地折叠了。 ## 练习 阅读`:help foldmethod`. 阅读`:help fold-manual`. 阅读`:help fold-marker`和`:help foldmarker`. 阅读`:help fold-indent`. 阅读`:help fdl`和`:help foldlevelstart`. 阅读`:help foldminlines`. 阅读`:help foldignore`.
';

更高级的语法高亮

最后更新于:2022-04-01 21:05:49

我们甚至可以为Vim里面的语法高亮另开一本书了。 我们将在此讲解它最后的重要内容,然后继续讲别的东西。 如果你想要学到更多,去读`:help syntax`并阅读别人写的syntax文件。 ## 高亮字符串 Potion,一如大多数编程语言,支持诸如`"Hello,world!"`的字符串字面量。 我们应该把这些高亮成字符串。为此我们将使用`syntax region`命令。 在你的Potion syntax文件中加入下面内容: ~~~ syntax region potionString start=/\v"/ skip=/\v\\./ end=/\v"/ highlight link potionString String ~~~ 关闭并重新打开你的`factorial.pn`,你将看到文件结尾的字符串被高亮了! 最后一行应该很熟了。如果你不懂,重读前两章。 第一行用一个"region"添加一个语法类型分组。 区域(Regions)有一个"start"模式和一个"end"模式来指定开头和结束的位置。 这里,一个Potion字符串从一个双引号开始,到另一个双引号结束。 `syntax region`的"skip"参数允许我们处理转义字符串,比如 `"She said: \"Vimscript is tricky, but useful\"!"`。 如果不提供`skip`参数,Vim将在`Vimscript`之前的`"`停止匹配字符串,这不是我们想要的! 简明扼要地说,`syntax region`中的`skip`参数告诉Vim: "一旦你开始匹配这个区域,我希望你忽略`skip`匹配的内容,即使它会被当作区域结束的标志"。 花上几分钟去想透彻。如果遇到的是`"foo \\" bar"`会怎样?那会是正确的行为吗? 那_总是_正确的行为吗?放下本书,花上几分钟来认真_想一想_! ## 练习 给单引号字符串加上语法高亮。 阅读`:help syn-region`. 阅读`:help syn-region`将比阅读本章花费更多的时间。给自己倒杯饮料,这是你应得的!
';

高级语法高亮

最后更新于:2022-04-01 21:05:47

目前我们已经为Potion文件实现了简单的关键字和函数的语法高亮。 如果没有做上一章的练习,你需要回去完成。我将假设你做了练习。 事实上,你应该回去完成你跳过的_任何_练习。即使你觉得你不需要,为了更好的学习效果, 你都得把它们完成了。请在这一点上相信我。 ## 高亮注释 接下来我们需要高亮Potion的一个重要组成部分——注释。 问题是,Potion的注释以`#`开头,而`#`并不在`iskeyword`里。 如果不知道什么是`iskeyword`,你没有认真听讲。回去并_完成那该死的练习_。 在写每一章的内容时,我不会把无意义的粗重活丢给你。你_真的_需要完成它们来跟上本书的进度。 因为`#`不是一个keyword字符,我们需要使用正则表达式来匹配它(以及接下来的注释)。 我们将用`syntax match`代替`syntax keyword`。在你的syntax文件中加入下面几行: ~~~ syntax match potionComment "\v#.*$" highlight link potionComment Comment ~~~ 我不会再唠叨要把它们放到文件的哪里。你已经是个程序猿了:由你自己判断。 关闭并重新打开`factorial.pn`。在文件的某处添加一个注释,你将看到它被当作注释高亮了。 第二行是简单的:它告诉Vim高亮`potionComment`语法类型组里的任何东西为`Comment`。 第一行有点新东西。我们使用`syntax match`来告诉Vim用_正则表达式_而不是关键词来匹配。 注意正则表达式以`\v`开头,表示使用"very magic"模式。 如果你不太清楚,重读关于基本正则表达式的那一章。(译注:第31章) 当前状况下,"very magic"模式不是必须的。 但将来我们可能会改变这个正则表达式,然后苦思冥想为何它不工作了, 所以我建议总是使用"very magic"来保证一致性。 至于正则表达式的功能,非常简单:匹配以`#`开头的注释,包括以此开始到行末的所有字符。 如果你需要重新唤起对正则表达式的记忆,你应该看一下 Zed Shaw的[Learn Regex the Hard Way](http://regex.learncodethehardway.org/)。 ## 高亮运算符 另一个需要正则表达式来高亮的部分是运算符。在你的syntax文件中加入下列内容: ~~~ syntax match potionOperator "\v\*" syntax match potionOperator "\v/" syntax match potionOperator "\v\+" syntax match potionOperator "\v-" syntax match potionOperator "\v\?" syntax match potionOperator "\v\*\=" syntax match potionOperator "\v/\=" syntax match potionOperator "\v\+\=" syntax match potionOperator "\v-\=" highlight link potionOperator Operator ~~~ 关闭并重新打开`factorial.pn`。注意到阶乘函数的`*=`现在被高亮了。 你可能首先注意到,我把每个正则表达式独立成一行而不是像对关键字一样分成组。 这是因为`syntax match`_不_支持在一行里放多个组。 你应该也注意到每个正则表达式都以`\v`开头,即使并不是必须的。 在写Vimscript时,我希望保持正则表达式的一致性,即使这样做需要多打几个符号。 你可能会奇怪,为什么我不用类似于`"\v-\=?"`的正则表达式来同时匹配`-`_以及_`-=`。 你想要的话也可以这么做。它会正常工作。 我只是坚持认为`-`和`-=`是不同的运算符,所以把它们放到不同行里。 把每个运算符放在单独的匹配中,简化了正则表达式,代价是输入了额外的字符。 我喜欢这么做,但你可能不这么认为。你自己决定吧。 我也没有把`=`定义成一个运算符。我们等会会这么做,但我希望暂时先不这样做,这样就能给你考上一题了。 因为分开了`-`和`-=`的正则表达式,我不得不在定义`-`_之后_定义`-=`! 如果以相反的顺序定义,并在Potion文件里使用`-=`,Vim将匹配`-`(当然,同时也高亮它), 剩下`=`等待匹配。这意味着在构建`syntax match`组时,每个组"消耗"的文本片段在之后不能被匹配到。 这讲得太笼统了,但我暂时并不打算纠结于细节。 总之,你应该在匹配较小的组之后匹配较大的组,因为在_之后_定义的组优先于在_之前_定义的组。 让我们继续并添加`=`作为运算符。现在请听题: ~~~ syntax match potionOperator "\v\=" ~~~ 花一点时间想想你应该把它放在syntax文件的哪个位置。如果你需要提示,重读前几章。 ## 练习 阅读`:help syn-match`. 阅读`:help syn-priority`. 在例子中,我们没有把`:`当作运算符。阅读Potion文档并审慎地决定是否把`:`当作一个运算符。 如果你决定这么做,把它加到syntax文件中。 同样考虑`.`和`/`。 增加一个高亮数字的语法类型分组`potionNumber`。链接它到高亮组`Number`。 不要忘了Potion支持`2`,`0xffaf`,`123.23`,`1e-2`和`1.9956e+2`这几种形式。 记得在处理边际状态的花费的时间和这些边际状态出现的次数之间取得平衡。
';

基本语法高亮

最后更新于:2022-04-01 21:05:44

既然已经移除前进路上的绊脚石,是时候开始为我们的Potion插件写下一些有用的代码。 我们将从一些简单的语法高亮开始。 在你的插件的repo中创建`syntax/potion.vim`。把下面的代码放到你的文件里: ~~~ if exists("b:current_syntax") finish endif echom "Our syntax highlighting code will go here." let b:current_syntax = "potion" ~~~ 关闭Vim,然后打开你的`factorial.pn`文件。 你也许或也许不能看到消息,取决于你是否有其他插件在该插件之后输出消息。 如果你执行`:message`,你将会看到这个文件的确已经加载了。 **注意:** 每次我告诉你打开Potion文件,我是想要你在一个新的Vim窗口或进程里打开,而不是在一个分割或tab。 打开一个新的Vim窗口导致Vim为此重新加载你所有的插件,而打开一个分割则不会。 代码文件开头和结尾的那几行是一个惯用法,如果这个缓冲区的语法高亮已经启动了,那就无需重新加载。 ## 高亮关键字 在本章的剩下部分,我们将忽略文件开头和结尾的`if`和`let`防御墙。不要移除那几行,只是眼不见为净而已。 用下面的代码替换掉文件中的占位符`echom`: ~~~ syntax keyword potionKeyword to times highlight link potionKeyword Keyword ~~~ 关闭`factorial.pn`并重新打开它。`to`和`times`被高亮成你的配色方案中的关键字类型了! 这两行展示了Vim中的基本的语法高亮。为了高亮某个语法: * 你首先要用`syntax keyword`或相关命令(我们待会会提到),定义一组语法类型。 * 然后你要把这组类型链接到高亮组(highlighting groups)。 一个高亮组是你在配色方案里定义的东西,比如"函数名应该是蓝色的"。 这可以让插件作者决定有意义的语法类型分组,然后链接到通用的高亮组。 这同时也让配色方案创作者决定通用的程序结构,而不需要考虑单独的语言。 除了在我们的玩具程序中用到的,Potion还有其他的关键字,所以让我们修改syntax文件来一并高亮它们。 ~~~ syntax keyword potionKeyword loop times to while syntax keyword potionKeyword if elsif else syntax keyword potionKeyword class return highlight link potionKeyword Keyword ~~~ 首先要说的是:最后一行没有改掉。我们依然告诉Vim所有在`potionKeyword`中的内容应该作为`Keyword`高亮。 我们现在新增三行,每行都以`syntax keyword potionKeyword`开头。 这意味着多次执行这个命令不会_重置_语法类型分组 —— 而是扩增它!这使得你可以化整为零地定义分组。 怎样定义分组取决于你: * 你可以仅仅一行密密麻麻地写满所有的内容。 * 你可以划分成几行,来满足每行80列的规则以便于阅读。 * 你可以每一项都独占一行,来使得diff的结果更加清晰。 * 你可以跟我在这里做的一样,把相关的项放在同一行。 ## 高亮函数 Vim的另一个高亮组是`Function`。这就来加入一些Potion的内置函数到我们的高亮文件。 把你的syntax文件修改成这样: ~~~ syntax keyword potionKeyword loop times to while syntax keyword potionKeyword if elsif else syntax keyword potionKeyword class return syntax keyword potionFunction print join string highlight link potionKeyword Keyword highlight link potionFunction Function ~~~ 关闭并重新打开`factorial.pn`,你将看到内置的Potion函数现在已经高亮了。 它的工作原理就跟关键字高亮一样。我们定义了新的语法类型分组并链接到不同的高亮组。 ## 练习 想一想为什么文件开头的`if exists`和结尾的`let`是有用的。如果你搞不懂,不要担心。 我也曾就这个问题问过Tim Pope。 浏览`:help syn-keyword`。注意提到`iskeyword`的部分。 阅读`:help iskeyword`. 阅读`:help group-name`来了解一些配色方案作者常用的通用高亮组。
';

检测文件类型

最后更新于:2022-04-01 21:05:42

让我们创建一个Potion文件作为插件的测试样本。 ~~~ factorial = (n): total = 1 n to 1 (i): total *= i. total. 10 times (i): i string print '! is: ' print factorial (i) string print "\n" print. ~~~ 这个代码创建了一个简单的阶乘函数并调用它10次,逐次输出结果。继续前进并用`potion factorial.pn`执行它。 输出结果应该像这样: ~~~ 0! is: 0 1! is: 1 2! is: 2 3! is: 6 4! is: 24 5! is: 120 6! is: 720 7! is: 5040 8! is: 40320 9! is: 362880 ~~~ 如果你得不到这个输出,或者你得到一个错误,停下来并排查问题所在。 这个代码应该会正常工作的。 这跟学习Vimscript没有关系,不过它能让你成为更好的程序猿。 ## 检测Potion文件 用Vim打开`factorial.pn`并执行下面命令: ~~~ :set filetype? ~~~ Vim将显示`filetype=`,因为它还不认识`.pn`文件。让我们解决这个问题。 在你的插件的repo中创建`ftdetect/potion.vim`。在它里面加入下面几行: ~~~ au BufNewFile,BufRead *.pn set filetype=potion ~~~ 这创建了一个单行自动命令:一个设置`.pn`文件的filetype为`potion`的命令。很简明吧。 注意我们_没有_像之前经常做的那样使用一个自动命令组。 Vim自动替你把`ftdetect/*.vim`文件包装成自动命令组,所以你不需要操心。 关闭`factorial.pn`并重新打开它。现在再执行前面的命令: ~~~ :set filetype? ~~~ 这次Vim显示`filetype=potion`。当Vim启动时,它加载`~/.vim/bundle/potion/ftdetect/potion.vim`里的自动命令组, 而当它打开`factorial.pn`时,自动命令起效,设置`filetype`为`potion`。 既然已经让Vim识别了Potion文件,我们可以继续前进来做些有用的东西了。 ## 练习 阅读`:help ft`。不要担心你看不懂里面的内容。 阅读`:help setfiletype`。 修改Potion插件中的`ftdetect/potion.vim`。 用`setfiletype`代替`set filetype`。
';

新希望:用Pathogen配置插件

最后更新于:2022-04-01 21:05:40

Vim的插件配置方式,在你仅仅添加一个文件来自定义自己的Vim体验时很合理, 但当你想要使用别人写的插件时,这种方式会导致一团糟。 在过去,要想使用别人写好的插件,你得下载所有文件并逐一正确地放置它们。 你也可能使用`zip`或`tar`来替你做放置的工作。 在这个过程中有些明显的问题: * 当你想更新插件的时候怎么办?你可以覆盖旧的文件, 但如果作者删除了某个文件,你怎么知道你要手工删除对应文件? * 假如有两个插件正好使用了同样的文件名(比如`utils.vim`或别的更大众的名字)呢? 有时你可以简单地重命名掉它,但如果它位于`autoload/`或别的名字相关的文件夹中呢? 你改掉文件名,就等于改掉插件。这一点也不好玩。 人们总结出一系列hacks来让事情变得简单些,比如Vimball。 幸运的是,我们不再需要忍受这些肮脏的hacks。 [Tim Pope](http://tpo.pe/)创造了著名的[Pathogen](http://www.vim.org/scripts/script.php?script_id=2332)插件让管理大量插件变得轻松愉快, 只要插件作者神志清醒地安排好插件结构。(译注:现在推荐[vundle](https://github.com/gmarik/vundle)来代替Pathogen,前者支持使用git下载插件) 让我们了解一下Pathogen的工作方式,以及为了让我们的插件更加兼容,我们需要做的事。 ## 运行时路径 当Vim在特殊的文件夹,比如`syntax/`,中查找文件时,它不仅仅只到单一的地方上查找。 就像Linux/Unix/BSD系统上的`PATH`,Vim设置`runtimepath`以便查找要加载的文件。 在你的桌面创建`colors`文件夹。在这个文件夹中创建一个叫`mycolor.vim`的文件(在本示例中你可以让它空着)。 打开Vim并执行这个命令: ~~~ :color mycolor ~~~ Vim将显示一个错误,因为它不懂得去你的桌面查找。现在执行这个命令: ~~~ :set runtimepath=/Users/sjl/Desktop ~~~ 当然,你得根据你的情况修改路径名。现在再尝试color命令: ~~~ :color mycolor ~~~ 这次Vim找到了`mycolor.vim`,所以它将不再报错。由于文件是空的,它事实上什么都没_做_, 但由于它不再报错,我们确信它找到了。 ## Pathogen Pathogen插件在你加载Vim的时候自动地把路径加到你的`runtimepath`中。 所有在`~/.vim/bundle/`下的文件夹将逐个加入到`runtimepath`。(译注:vundle也是这么做的) 这意味着每个`bundle/`下的文件夹应该包括部分或全部的标准的Vim插件文件夹,比如`colors/`和`syntax/`。 现在Vim可以从每个文件夹中加载文件,而且每个插件文件都独立于自己的文件夹中。 这么一来更新插件就轻松多了。你只需要整个移除旧的插件文件夹,并迎来新的版本。 如果你通过版本控制来管理`~/.vim`文件夹(你应该这么做), 你可以使用Mercurial的subrepo或Git的submodule功能来直接签出(checkout)每个插件的代码库, 然后用一个简单的`hg pull; hg update`或`git pull origin master`来更新。 ## 成为Pathogen兼容的 我们计划让我们的用户通过Pathogen安装我们写的Potion插件。 我们需要做的:在插件的代码库里,放置我们的文件到正确的文件夹中。就是这么简单! 我们插件的代码库展开后看起来就像这样: ~~~ potion/ README LICENSE doc/ potion.txt ftdetect/ potion.vim ftplugin/ potion.vim syntax/ potion.vim ... etc ... ~~~ 我们把它放置在GitHub或Bitbucket上,这样用户就能简单地clone它到`bundle/`,一切顺利! ## 练习 如果你还没有安装[vnudle][],安装它。(译注:原文是安装[Pathogen](http://www.vim.org/scripts/script.php?script_id=2332),但是没有必要啦) 给你的插件创建Mercurial或Git代码库,起名叫`potion`。 你可以把它放到你喜欢的地方,并链接到`~/.vim/bundle/potion/`或就把它直接放到`~/.vim/bindle/potion/`。 在代码库中创建`README`和`LICENSE`文件,然后commit。 Push到Bitbucket或GitHub。 阅读`:help runtimepath`。
';

旧社会下的插件配置方式

最后更新于:2022-04-01 21:05:38

我们需要讲到的第一件事是如何配置我们的插件。在过去,这会是一次混乱的折腾, 但现在我们有一个工具可以非常方便地安装Vim插件。 我们需要先过一下基本的配置方式,然后我们会讲到如何省下麻烦。 ## 基本配置方式 Vim支持把插件分割成多个文件。你可以在`~/.vim`下创建许多不同种类的文件夹来放置不同的内容。 我们现在将讲述其中最为重要的几个文件夹,但不会在上面花费太多时间。 当我们创造Potion插件时,我们会逐一认识它们的。 在我们继续前进之前,需要先确定一些用词规范。 我将用"插件"表示一大堆做一系列相关事情的Vimscript代码。 在Vim里,"插件(plugin)"有一个更专业的定义,它表示"`~/.vim/plugins/`下的一个文件"。 在大多数时间里,我将使用第一个定义。如果指的是第二个定义,我会特意指明。 ## ~/.vim/colors/ Vim将会查找`~/.vim/colors/mycolors.vim`并执行它。 这个文件应该包括生成你的配色方案所需的一切Vimscript命令。 本书中,我们不会谈到配色方案。如果想创造属于自己的配色方案,你应该从一个现存的配色方案上改造出来。 记住,`:help`将与你常在。 ## ~/.vim/plugin/ `~/.vim/plugin/`下的文件将在_每次_Vim启动的时候执行。 这里的文件包括那些无论何时,在启动Vim之后你就想加载的代码。 ## ~/.vim/ftdetect/ `~/.vim/ftdetect/`下的文件在每次你启动Vim的时候_也会_执行。 `ftdetect`是"filetype detection"的缩写。 这里的文件_仅仅_负责启动检测和设置文件的`filetype`类型的自动命令。 这意味着它们一般不会超过一两行。 ## ~/.vim/ftplugin/ `~/.vim/ftplugin/`下的文件则各不相同。 一切皆取决于它的名字!当Vim把一个缓冲区的`filetype`设置成某个值时, 它会去查找`~/.vim/ftplugin/`下对应的文件。 比如:如果你执行`set filetype=derp`,Vim将查找`~/.vim/ftplugin/derp.vim`。 一旦文件存在,Vim将执行它。 Vim也支持在`~/.vim/ftplugin/`下放置文件夹。 再以我们刚才的例子为例:`set filetype=derp`将告诉Vim去执行`~/.vim/ftplugin/derp/`下的全部`*.vim`文件。 这使得你可以按代码逻辑分割在`ftplugin`下的文件。 因为每次在一个缓冲区中执行`filetype`时都会执行这些文件,所以它们_只能_设置buffer-local选项! 如果在它们中设置了全局选项,所有打开的缓冲区的设置都会遭到覆盖! ## ~/.vim/indent/ `~/.vim/indent/`下的文件类似于`ftplugin`下的文件。加载时也是只加载名字对应的文件。 `indent`文件应该设置跟对应文件类型相关的缩进,而且这些设置应该是buffer-local的。 是的,你当然可以把这些代码也一并放入`ftplugin`文件, 但最好把它们独立出来,让其他Vim用户理解你的意图。这只是一种惯例,不过请尽量体贴用户并遵从它。 ## ~/.vim/compiler/ `~/.vim/compiler`下的文件非常类似于`indent`文件。它们应该设置同类型名的当前缓冲区下的编译器相关选项。 不要担心不懂什么是"编译器相关选项"。我们等会会解释。 ## ~/.vim/after/ `~/.vim/after`文件夹有点神奇。这个文件夹下的文件会在每次Vim启动的时候加载, 不过是在`~/.vim/plugin/`下的文件加载了_之后_。 这允许你覆盖Vim的默认设置。实际上你将很少需要这么做,所以不用理它, 除非你有"Vim设置了选项`x`,但我想要不同的设置"的主意。 ## ~/.vim/autoload/ `~/.vim/autoload`文件夹就更加神奇了。事实上它的作用没有听起来那么复杂。 简明扼要地说:`autoload`是一种延迟插件代码到需要时才加载的方法。 我们将在重构插件的时候详细讲解并展示它的用法。 ## ~/.vim/doc/ 最后,`~/.vim/doc/`文件夹提供了一个你可以放置你的插件的文档的地方。 Vim对文档的要求是多多益善(看看我们执行过的所有`:help`命令就知道),所以为你的插件写文档是重要的。 ## 练习 重读本章。我没开玩笑。确保你(大体上)明白我们讲过的每一个文件夹。 作为额外的加分,找一些你正在用的Vim插件看看它们如何组织代码文件。
';

创建一个完整的插件

最后更新于:2022-04-01 21:05:35

在前四十来章中,我们讲解了许多基础方面的内容。 在本书的最后部分,我们将尝试从零开始为一门语言创造Vim插件。 这不是个适合懦夫的游戏。这将需要你竭尽全力。 如果你现在就想退出,那确实也不坏!你已经学到了如何在`~/.vimrc`里改善你的生活, 还有如果修复别人的插件里的bugs。 有"这就够了,我不想虚掷光阴于创造一个我将不会使用的插件"这种想法并不可耻。 现实一点。如果你不想创造一个自己想用的插件,现在就可以离开,到你想要的时候再回来吧。 如果你_真的_想要继续,确保你可以挤出一些时间。本书剩余部分将会显得困难, 而且我会假定你真的想_学点东西_,而不是仅仅慵懒地一章章翻过去。 ## Potion 我们创造的插件将为[Potion](http://fogus.github.com/potion/index.html)这门语言提供支持。 Potion是由`Why the lucky stiff`在隐于江湖之前(before his disappearance)创建的一门玩具语言。 它非常的简单,所以我们就拿它一试身手。 Potion跟[Io](http://iolanguage.com/)很像,同时又借鉴了Ruby,Lua以及其他语言。如果你未曾玩过Io,它可能看上去略古怪。 我强烈推荐你花上至少一两个小时的时间玩玩Potion。在现实生活中你不会用它, 但是它可能会改变你思考的方式并带给你新的思想。 Potion的当前实现相当地粗糙。举个例子:如果你犯了语法错误,它通常会还你段错误。 不要太纠结于此。我会给你许多可用的代码示范,这样你就能更关注于Vimscript本身而非Potion。 我们的目标不是学习Potion(尽管那也挺有趣)。 我们的目标是以Potion作为一个小例子来体验写一个完整的Vim插件的方方面面。 ## 练习 下载并安装[Potion](http://fogus.github.com/potion/index.html)。这个就要你自己动手了。它应该会比较简单的。 确保你可以在Potion解释器和以`.pn`文件的形式运行小册子里的第一个示例代码。 如果解释器貌似不能工作,看[这个issue](https://github.com/fogus/potion/issues/12)来查找可能的原因。
';

路径

最后更新于:2022-04-01 21:05:33

Vim是一个文本编辑器,而文本编辑器(经常)处理文本文件。文本文件储存在文件系统中, 而我们使用路径来描述文件。Vimscript有一些内置的方法会在你需要处理路径时帮上大忙。 ## 绝对路径 有时外部脚本也需要获取特定文件的绝对路径名。执行下面的命令: ~~~ :echom expand('%') :echom expand('%:p') :echom fnamemodify('foo.txt', ':p') ~~~ 第一个命令显示我们正在编辑的文件的相对路径。`%`表示"当前文件"。 Vim也支持其他一些字符串作为`expand()`的参数。 第二个命令显示当前文件的完整的绝对路径名。字符串中的`:p`告诉Vim你需要绝对路径。 这里也有许多别的修饰符可以用到。 第三个命令显示了当前文件夹下的文件`foo.txt`的绝对路径,无论文件是否存在。(译注:试一下看看文件不存在的情况?) `fnamemodify()`是一个比`expand()`灵活多了的Vim函数, 你可以指定任意文件名作为`fnamemodify()`的参数,而不仅仅是`expand()`所需要的那种特殊字符串。 ## 列出文件 你可能想要得到一个特定文件夹下的文件列表。执行下面的命令: ~~~ :echo globpath('.', '*') ~~~ Vim将输出当前目录下所有的文件和文件夹。`globpath()`函数返回一个字符串, 其中每一项都用换行符隔开。为了得到一个列表,你需要自己去`split()`。执行这个命令: ~~~ :echo split(globpath('.', '*'), '\n') ~~~ 这次Vim显示一个包括各个文件路径的Vimscript列表。 如果你的文件名里包括了换行符,那就只能由你自己想办法了。 `globpath()`的通配符(wildcards)的工作方式就像你所想的一样。执行下面的命令: ~~~ :echo split(globpath('.', '*.txt'), '\n') ~~~ Vim显示一个当前文件夹下的所有`.txt`文件组成的列表。 你可以用`**`递归地列出文件。执行这个命令: ~~~ :echo split(globpath('.', '**'), '\n') ~~~ Vim将列出当前文件夹下的所有文件及文件夹。 `globpath()`_非常地_强大。在你完成本章练习后,你将学到更多内容。 ## 练习 阅读`:help expand()`. 阅读`:help fnamemodify()`. 阅读`:help filename-modifiers`. 阅读`:help simplify()`. 阅读`:help resolve()`. 阅读`:help globpath()`. 阅读`:help wildcards`.
';

函数式编程

最后更新于:2022-04-01 21:05:31

现在让我们小憩一下,聊一聊一种你可能听过的编程风格:[函数式编程](https://secure.wikimedia.org/wikipedia/en/wiki/Functional_programming)。 如果你用过Python,Ruby或Javascript,_甚或_Lisp,Scheme,Clojure或Haskell, 你应该会觉得把函数作为变量类型,用不可变的状态作为数据结构是平常的事。 如果你没用过,你可以放心地跳过这一章了,但我还是鼓励你找机会去试试并拓宽自己的视野。 Vimscript具有使用函数式风格进行编程的潜力,不过会有点吃力。 我们可以创建一些辅助函数来让这个过程少些痛苦。 继续前进并创建`functional.vim`文件,这样你就不用反复地重新击打每一行代码。 这个文件将会成为这一章的草稿本。 ## 不可变的数据结构 不幸的是,Vim没有类似于Clojure内置的vector和map那样的不可变集合, 不过通过一些辅助函数,我们可以在一定程度上模拟出来。 在你的文件加上下面的函数: ~~~ function! Sorted(l) let new_list = deepcopy(a:l) call sort(new_list) return new_list endfunction ~~~ 保存并source文件,然后执行`:echo Sorted([3,2,4,1])`来试试看。 Vim输出`[1,2,3,4]`。 这跟调用内置的`sort()`函数有什么区别呢?关键在于第一行:`let new_list = deepcopy(a:l)`。 Vim的`sort()`_就地_重排列表,所以我们先创建一个列表的副本,并排序_副本_, 这样原本的列表不会被改变。 这样就避免了副作用,并帮助我们写出更容易推断和测试的代码。让我们加入更多同样风格的辅助函数: ~~~ function! Reversed(l) let new_list = deepcopy(a:l) call reverse(new_list) return new_list endfunction function! Append(l, val) let new_list = deepcopy(a:l) call add(new_list, a:val) return new_list endfunction function! Assoc(l, i, val) let new_list = deepcopy(a:l) let new_list[a:i] = a:val return new_list endfunction function! Pop(l, i) let new_list = deepcopy(a:l) call remove(new_list, a:i) return new_list endfunction ~~~ 除了中间的一行和它们接受的参数,每一个函数都是一样的。保存并source文件,在一些列表上试试它们。 `Reversed()`接受一个列表并返回一个新的倒置了元素的列表。 `Append()`返回一个在原列表的基础上增加了给定值的新列表。 `Assoc()`("associate"的缩写)返回一个给定索引上的元素被替换成新值的新列表。 `Pop()`返回一个给定索引上的元素被移除的新列表。 ## 作为变量的函数 Vimscript支持使用变量储存函数,但是相关的语法有点愚钝。执行下面的命令: ~~~ :let Myfunc = function("Append") :echo Myfunc([1, 2], 3) ~~~ Vim意料之中地显示`[1, 2, 3]`。注意我们使用的变量以大写字母开头。 如果一个Vimscript变量要引用一个函数,它就要以大写字母开头。 就像其他种类的变量,函数也可以储存在列表里。执行下面命令: ~~~ :let funcs = [function("Append"), function("Pop")] :echo funcs[1](['a', 'b', 'c'], 1) ~~~ Vim显示`['a', 'c']`。`funcs`变量_不_需要以大写字母开头,因为它储存的是列表,而不是函数。 列表的内容不会造成任何影响。 ## 高阶函数 让我们创建一些用途广泛的高阶函数。如果你需要解释,高阶函数就是接受_别的_函数并使用它们的函数。 我们将从`map`函数开始。在你的文件中添加这个: ~~~ function! Mapped(fn, l) let new_list = deepcopy(a:l) call map(new_list, string(a:fn) . '(v:val)') return new_list endfunction ~~~ 保存并source文件,执行下面命令试试看: ~~~ :let mylist = [[1, 2], [3, 4]] :echo Mapped(function("Reversed"), mylist) ~~~ Vim显示`[[2, 1], [4, 3]]`,正好是对列表中的每一个元素应用了`Reversed()`的结果。 `Mapped()`是如何起作用的?我们又一次用`deepcopy()`创建新的列表,修修改改,返回修改后的副本 —— 没什么是新的。有门道的是中间的部分。 `Mapped()`接受两个参数:一个funcref("储存一个函数的变量"在Vim里的说法)和一个列表。 我们使用内置的`map()`函数实现真正的工作。现在就阅读`:help map()`来看它怎么工作的。 现在我们将创建一些通用的高阶函数。把下面的代码加入到你的文件: ~~~ function! Filtered(fn, l) let new_list = deepcopy(a:l) call filter(new_list, string(a:fn) . '(v:val)') return new_list endfunction ~~~ 用下面的命令尝试`Filtered()`: ~~~ :let mylist = [[1, 2], [], ['foo'], []] :echo Filtered(function('len'), mylist) ~~~ Vim显示`[[1, 2], ['foo']]`。 `Filtered()`接受一个谓词函数和一个列表。它返回一个列表的副本, 而这个列表只包括将自身作为谓词函数的输入参数并返回真值的元素。 这里我们使用了内置的`len()`,让它过滤掉所有长度为0的元素。 最后我们创建了`Filtered()`的好基友(counterpart): ~~~ function! Removed(fn, l) let new_list = deepcopy(a:l) call filter(new_list, '!' . string(a:fn) . '(v:val)') return new_list endfunction ~~~ 像使用`Filtered()`一样试一下: ~~~ :let mylist = [[1, 2], [], ['foo'], []] :echo Removed(function('len'), mylist) ~~~ Vim显示`[[], []]`。`Removed()`就像`Filtered()`,不过它只保留谓词函数返回_非_真值的元素。 代码中的唯一不同在于调用命令前面的`'!' .`,它把谓词函数的结果取反。 ## 效率 考虑到Vim不得不持续地创建新的副本并垃圾回收旧的对象,你可能会认为不停地制造副本是种浪费。 是的,你是对的!Vim的列表不像Clojure的vector那样支持结构共享(structural sharing), 所以这里所有的复制操作是昂贵的。 有时这的确是个问题。如果你需要使用庞大的列表,程序就会因此变慢。 在现实世界,你可能会吃惊地发现你几乎不会注意到其中的差别。 想想看吧:当我正写下本章时,Vim占用了80M内存(而且我可是装了_一堆_插件)。 我的笔记本总共有_8G_内存。有一些列表的副本被创建出来,这会造成可被察觉的不同吗? 当然这取决于列表的大小,但在大多数情况下答案将会是"No"。 作为比较,我的Firefox打开了五个tab,现在正饕餮着_1.22G_内存。 你将需要自己判断,什么时候这种编程风格会导致不可接受的低效率。 ## 练习 阅读`:help sort()`。 阅读`:help reverse()`。 阅读`:help copy()`。 阅读`:help deepcopy()`。 阅读`:help map()`,如果你未曾读过。 阅读`:help function()`。 修改`Assoc()`, `Pop()`, `Mapped()`, `Filtered()`和`Removed()`来支持字典类型。 你可能需要阅读`:help type()`来帮助自己。 实现`Reduced()`。 倒给自己一杯最喜欢的饮料。这一章真激烈(intense)!
';

切换

最后更新于:2022-04-01 21:05:28

在开头前几章我们曾讲过怎么在Vim里设置选项。 对于布尔选项,我们可以使用`set someoption!`来"切换"选项。 如果我们能给这个命令创建一个映射,那就再好不过了。 执行下面的命令: ~~~ :nnoremap N :setlocal number! ~~~ 在normal模式中按下`N`看看。Vim将会在开启和关闭行号显示之间切换。 像这样的"切换"映射是十分方便的,因此我们就不需要两个独立的键来开/关。 不幸的是,这只对布尔选项起作用。如果我们想要切换一个非布尔选项,还需要做更多的工作。 ## 切换选项 从创建一个可以切换选项的函数,以及调用该函数的映射开始吧。 把下面的代码加入到你的`~/.vimrc`(或一个`~/.vim/plugin/`中的独立文件,如果你想要的话): ~~~ nnoremap f :call FoldColumnToggle() function! FoldColumnToggle() echom &foldcolumn endfunction ~~~ 保存并source文件,然后按下`f`试试看。Vim显示当前`foldcolumn`选项的值。 如果你不熟悉这个选项,阅读`:help foldcolumn`再继续。 让我们添加真正的切换功能。修改代码成这样: ~~~ nnoremap f :call FoldColumnToggle() function! FoldColumnToggle() if &foldcolumn setlocal foldcolumn=0 else setlocal foldcolumn=4 endif endfunction ~~~ 保存并source文件,然后试试看。每次你按下它Vim将显示或隐藏折叠状态条(fold column)。 `if`语句判断`&foldcolumn`是否为真(记住Vim把0看作假而其他数字为真)。 如果是,把它设成0(隐藏它)。否则就设置它为4。就是这么简单。 你可以使用一个简单的函数像这样来切换任何以`0`代表关,以其他数字代表开的选项。 ## 切换其他东西 我们的梦想不应止于切换选项。还有一个我们想切换的东西是quickfix窗口。 依然以之前的骨架代码作为起点。加入下面的代码到你的文件: ~~~ nnoremap q :call QuickfixToggle() function! QuickfixToggle() return endfunction ~~~ 这个映射暂时什么都不干。让我们把它转变成其他稍微有点用的东西(不过还没有彻底完成)。 把代码改成这样: ~~~ nnoremap q :call QuickfixToggle() function! QuickfixToggle() copen endfunction ~~~ 保存并source文件。如果现在你试一下这个映射,你就会看到一个空荡荡的quickfix窗口。 为了达到实现切换功能的目的,我们将选择一个既快捷又肮脏的手段:全局变量。 把代码改成这样: ~~~ nnoremap q :call QuickfixToggle() function! QuickfixToggle() if g:quickfix_is_open cclose let g:quickfix_is_open = 0 else copen let g:quickfix_is_open = 1 endif endfunction ~~~ 我们干的事情十分简单 —— 每次调用函数时,我们用一个全局变量来储存quickfix窗口的开关状态。 保存并source文件,接着执行映射试试看。Vim将抱怨变量尚未定义!那么我们先把变量初始化吧。 ~~~ nnoremap q :call QuickfixToggle() let g:quickfix_is_open = 0 function! QuickfixToggle() if g:quickfix_is_open cclose let g:quickfix_is_open = 0 else copen let g:quickfix_is_open = 1 endif endfunction ~~~ 保存并source文件,接着试一下映射。成功了! ## 改进 我们的切换函数可以工作,但还留有一些问题。 第一个问题是,假设用户用`:copen`或`:cclose`手动开关窗口,我们的全局变量将不会刷新。 实际上这不会是个大问题,因为大多数情况下用户会用这个映射开关窗口,万一没有打开,他们也会再按一次。 这又是关于写Vimscript代码的重要经验:如果你试图处理每一个边际条件,你将陷在里面,而且不会有任何进展。 在大多数情况下,先推出可工作(而且即使不能工作也不会造成破坏)的代码然后回过头改善, 要比耗费许多小时苛求完美好得多。除外你正在开发一个很可能有很多人用到的插件。 在这种情况下它才值得耗费时日来达到无懈可击的程度,让用户满意并减少bug报告。 ## 重新加载窗口/缓冲区 我们的函数的另外一个问题是,当用户已经打开了quickfix窗口,并执行这个映射时, Vim关闭了窗口,接着把他们弹到上一个分割中,而不是送他们回之前的地方。 如果你仅仅想快速查看一下quickfix窗口然后继续工作,发生这种事是让人恼怒的。 为了解决这个问题,我们将引入一种写Vim插件时非常有用的惯用法。把你的代码改成这样: ~~~ nnoremap q :call QuickfixToggle() let g:quickfix_is_open = 0 function! QuickfixToggle() if g:quickfix_is_open cclose let g:quickfix_is_open = 0 execute g:quickfix_return_to_window . "wincmd w" else let g:quickfix_return_to_window = winnr() copen let g:quickfix_is_open = 1 endif endfunction ~~~ 我们在映射中加入了新的两行。其中一行(在`else`分支)设置了另一个全局变量,来保存执行`:copen`时的当前窗口序号。 另一行(在`if`分支)执行以那个序号作前缀的`wincmd w`,来告诉Vim跳转到对应窗口。 我们的解决方法又一次不是无懈可击的,用户可能在两次执行映射之间打开或关闭新的分割。 即使这样,它还是适合于大多数场合,所以目前这已经够好的了。 在大多数程序中,这种手工保存全局状态的伎俩会遭到谴责,但对于一个非常短小的Vimscript函数而言, 它既快捷又肮脏,却能不辱使命,完成重任。 ## 练习 阅读`:help foldcolumn`. 阅读`:help winnr()` 阅读`:help ctrl-w_w`. 阅读`:help wincmd`. 在需要的地方加上`s:`和``来把函数限定在独自的命名空间中。
';

字典

最后更新于:2022-04-01 21:05:26

我们讲到的最后一种Vimscript类型将是字典。 Vimscript字典类似于Python中的dict,Ruby中的hash,和Javascript中的object。 字典用花括号创建。值是异质的,但_键会被强制转换成字符串_。就是这么简单,你没想到吧? 执行这个命令: ~~~ :echo {'a': 1, 100: 'foo'} ~~~ Vim显示`{'a':1,'100':'foo'}`,这说明Vimscript的确把键强制转换为字符串,同时保留值不变。 Vimscript避免了Javascript标准的蠢笨之处,允许你在字典的最后一个元素后留下一个逗号。 (译注:在Javascript的标准中,最后一个元素后面不能留下一个逗号。 但在Firefox里,留下那个逗号是允许的,不过这是Firefox的问题。) 执行下面的命令: ~~~ :echo {'a': 1, 100: 'foo',} ~~~ Vim再次显示`{'a':1,'100':'foo'}`(译注:结尾小逗号不见了)。你应该_总是_在字典里留下一个多余的逗号, _尤其_是当字典的定义跨越多行的时候,这样增加新项的时候将不容易犯错。 ## 索引 查找字典中的一个值的语法跟大多数语言是一样的。执行这个命令: ~~~ :echo {'a': 1, 100: 'foo',}['a'] ~~~ Vim显示`1`。试试使用不是字符串的索引: ~~~ :echo {'a': 1, 100: 'foo',}[100] ~~~ Vim会在查找之前把索引强制转换成字符串,因为键只能是字符串,这么做是合理的。 当键仅由字母,数字和/或下划线组成时,Vimscript也支持Javascript风格的"点"查找。 试试下面的命令: ~~~ :echo {'a': 1, 100: 'foo',}.a :echo {'a': 1, 100: 'foo',}.100 ~~~ 两种情况下,Vim都显示了正确的元素。使用哪种索引字典的方式取决于你自己的偏好。 ## 赋值和添加 像对待变量一样赋值给字典中的项,就可以在字典中轻松地添加新的项。 ~~~ :let foo = {'a': 1} :let foo.a = 100 :let foo.b = 200 :echo foo ~~~ Vim显示`{'a': 100, 'b': 200}`。赋值和添加一个新项的方式是一样的。 ## 移除项 有两种方法可以移除字典中的项。执行下面的命令: ~~~ :let test = remove(foo, 'a') :unlet foo.b :echo foo :echo test ~~~ Vim显示`{}`和`100`。`remove`函数将移除给定字典的给定键对应的项,并返回被移除的值。 `unlet`命令也能移除字典中的项,只是不返回值。 你不能移除字典中不存在的项。试试执行这个命令: ~~~ :unlet foo["asdf"] ~~~ Vim抛出一个错误。 选择`remove`还是`unlet`很大程度上取决于个人偏好。如果非要我说,我推荐使用`remove`, 因为它比`unlet`更灵活。`remove`可以做任何`unlet`能做的事,反过来不成立。 所以选择`remove`可以一招鲜,吃遍天。 ## 字典函数 就像列表,Vim有许许多多内置的字典函数。执行下面的命令: ~~~ :echom get({'a': 100}, 'a', 'default') :echom get({'a': 100}, 'b', 'default') ~~~ Vim显示`100`和`default`,如同列表版本的`get`函数. 你也可以检查给定字典里是否有给定的键。执行这个命令: ~~~ :echom has_key({'a': 100}, 'a') :echom has_key({'a': 100}, 'b') ~~~ Vim显示`1`和`0`。不要忘了,Vimscript把`0`当作假而其他数字则是真。 你可以用`items`从一个字典中获取对应的键值对,执行这个命令: ~~~ :echo items({'a': 100, 'b': 200}) ~~~ Vim将显示`[['a',100],['b',200]]`这样的嵌套列表。到目前为止,Vimscript字典_不一定_是有序的, 所以不要指望`items`的返回结果是有序的! 你可以用`keys`返回字典的所有的键和`values`返回所有的值。它们的作用一如其名——你可以查一下。 ## 练习 阅读`:help Dictionary`。看完它。注意大写`D`。 阅读`:help get()`. 阅读`:help has_key()`. 阅读`:help items()`. 阅读`:help keys()`. 阅读`:help values()`.
';