develop

develop.txt 适用于 Vim 9.0 版本。 最近更新: 2023年2月 VIM 参考手册 by Bram Moolenaar 译者: jwdu、iCrazy、tocer Vim 的开发 development 这一节的内容对那些想进一步开发 Vim 的人来说是很重要的。 1. 设计目标 design-goals 2. 编程风格 coding-style 3. 设计决定 design-decisions 4. 假定 design-assumptions 请先看一下 "src" 目录下的 README.txt 以便对源代码有一个总体的了解。 Vim 是开源软件,鼓励所有人参与并改进 Vim。如果你想递交 patch,请使用 "diff -u" 来产生合并 (unified) 格式的 diff 文件。 另见 http://vim.wikia.com/wiki/How_to_make_and_submit_a_patch

1. 设计目标 design-goals

首先说最重要的内容 (概略的说一下)。 注意 许多项目是互相冲突的,但这是故意的。对于它们,你必须自己平衡一下。 VIM 是 ... 和 VI 兼 容 的 design-compatible 首先,必须使 Vim 能够代替 Vi。如果用户愿意,他可以在兼容模式下使用 Vim 而且很 难发现它和原先的 Vi 有什么不同。 例外: - 在 Vim 中不再有明显的 Vi 错误。 - 有很多不同的 Vi 版本。我用 3.7 (6/7/85) 版作为参考。但是如果可能,也会支持其 它的版本。POSIX 中的 Vi 部分不是最主要的来源。 - Vim 中有一些新的指令,你不要因为某个命令在 Vi 中不存在,就指望它会失败。 - Vim 有许多特性是 Vi 所没有的。从 Vim 回到 Vi ,将会出现一些不可避免的问题。 - 有一些指令可能是很少用到 (open 模式、在崩溃时发送一封邮件等等)。只有你能说明 为什么应当加入这些功能,并且不会增加多少代码量时,它们才会被加入。 - 对一些命令是否仍需兼容 Vi 是一个有争议的问题。对这些,会使用一个选项标志位。 VIM 是 ... 改 进 过 的 design-improved Vim 的改良使它成为一个更好的 Vi,而没有变成一个完全不同的编译器。对它的扩展是 本着 "Vi 精神" 完成的。 - 尽可能多的使用键盘,鼠标需要第三只手来操作,而我们没有第三只手。而且,许多终 端没有鼠标。 - 当使用鼠标操作时,尽量少回到键盘操作,避免鼠标和键盘的混合操作。 - 用一致的方式增加命令和选项。否则,人们将会很难发现并记住它们。请记住: 以后会 加入更多的命令和选项。 - 一个人们不知道的特性是一个无用的特性。不要添加不明显的特性,或者至少在文档中 说明它们的存在。 - 尽量少用 CTRL 和其它修饰符,因为它们很难输入。 - 因为对许多人来说,Vim 是他们刚接触或不熟悉的,所以请尽量使 Vim 容易上手。 - 特性是可以无限增加的,对它们的选择基于以下方面: (1) 使用者需要什么 (2) 实现 它需要多少工作量 (3) 的确有人会去实现它。 VIM 是 ... 多 平 台 的 design-multi-platform Vim 设法支持尽可能多的平台,从而让尽可能多的人能够使用它。 - 支持多种终端。最小的需求是定位鼠标和清屏。指令最好用多数键盘都具有的那些键来 执行。在映射键盘时,支持键盘上所有的键。 - 支持多种平台。必要条件是有人想在此平台上开发 Vim,而且不要把代码搞乱。 - 支持多种编译器和库。并不是每个人都能够安装另一个编译器或图形库。 - 人们经常从一个平台转移到另一个平台,从图形界面转移到终端模式。特性应在每个版 本中都加入,至少应该在尽可能多的版本中出现。尽量避免使用者为了有效的完成他们 的工作而需要在不同平台间转移。 - 也许有些特性在某些平台上没有,或者只在某个平台上有,但这不意味着它不应当被实 现。[这个和上一个原则明显的冲突了,但我们必须在它们之间做个平衡。] VIM 是 ... 具 有 详 尽 文 档 的 design-documented - 一个没有文档的特性是一个无用的特性。针对新特性的补丁必须包含它的文档。 - 文档必须全面且容易理解。最好举例说明问题。 - 请使文件尽可能的短,短文档能使得找到它容易一些。 VIM 是 ... 速 度 快 、 体 积 小 的 design-speed-size Vim 不能大量消耗系统资源,必须使它体积小且速度快。 - 计算机每年都在变得更快、更大。Vim 也可以同样的变化,但是速度不能超过计算机的 变化速度。要使 Vim 在老的计算机上也可以使用。 - 许多使用者经常从外壳启动 Vim。启用时间必须尽量短。 - 指令必须有效的工作,它们消耗的时间要尽可能的短。有用的指令可以多消耗一点时 间。 - 注意到许多人在慢速线路上使用 Vim,因此,要使通信的开销尽可能的少。 - 如果一个显著增加 Vim 体积的功能不被大多数人使用,应该可以关闭该功能。 - Vim 只是其它许多组件中的一个,不要把它弄成一个笨重的应用程序,倒要使它能够和 其它程序一起很好的工作。 VIM 是 ... 可 维 护 的 design-maintain - 源代码不应该变成一团糟。它应该是可靠的代码。 - 所有的文件应使用相同的风格,使得它们易读性强 coding-style 。 - 根据需要有效的使用注释! 不要 引用函数和参数名。要解释它们的意义。 - 移植到另一个平台应该很容易,而不必改变太多独立于平台的代码。 - 使用面向对象的思想: 把数据和代码放在一起,波及到代码其它部分的知识要尽可能 的少。 VIM 是 ... 灵 活 的 design-flexible Vim 应该让使用它的人很容易在他们喜欢的风格下工作,而不强迫他们使用一个特定的方 式工作。这个对于那些有巨大影响的选项 (如 'compatible' 选项) 和其他细节都是一 样。缺省值是经过认真挑选的,以便大多数使用者都乐意在默认设置下使用 Vim。用户可 以用命令和选项来调整 Vim 来满足他们和环境的需要。 VIM 不 是 ... design-not - Vim 不是一个外壳或操作系统。它确实提供自己的终端窗口,可在其中运行外壳或调试 噐,以便于在 ssh 会话中应用。但如果不需要文本编辑,这些功能就超出了它的应用 范围 (请改用 screen 或 tmux 这样的工具)。 这里有一个讽刺的说法: "Vim 不像 Emacs 那样会包含除厨房中的水槽以外的每件事 物,但有人说如果你需要的话,却可以用它来洗盘子。;-)" 要在 Vim 中使用 gdb,参见 terminal-debugger 。其它 (老式的) 工具可见: http://www.agide.orghttp://clewn.sf.net。 - Vim 不是一个为了看起来漂亮而牺牲多平台一致性的华而不实的 GUI 编辑器。但我们 依然欢迎有效的 GUI 特性。

2. 编程风格 coding-style

这些是改变 Vim 源代码的时候必须遵循的准则。为了保持源代码的可读性和可维护性, 请坚持使用这些原则。 这个列表是不完全的,请查看源代码以获得更多的例子。 修 改 代 码 style-changes 修改代码的基本步骤: 1. 从 github 获取代码。方便你的版本随时和主代码库保持同步 (你的修改可能需要一 段时间才会被接受)。需要花点时间学习 git,它的用户界面不是最直观。 2. 首先调整文档,这样做将使你对你的更改如何影响使用者有一个印象。 3. 改变源代码。 4. 检查 ../doc/todo.txt,看看所作的修改是否影响其中的项目。 5. 用 "git diff" 做一个补丁。也可以在 github 上创建拉取请求,但主要是 diff 的 部分。 6. 写一个修改了哪些地方的记录,最好提到相关问题和解决方案。给 vim-dev 邮件组发 信包含这个解释和 diff 本身。也可以在 github 上创建拉取请求。 C COMPILER style-compiler ANSI-C C89 C99 支持的 C 编译器最低版本是 C89,也即 ANSI C。之后的标准,如 C99,还没有得到广泛 的支持,或至少没有得到 100% 的支持。因此我们只使用部分 C99 特性而显式地禁止其 它的一些特性 (随着时间推移会作调整)。 请不要在任何地方修改代码来应用 C99 特性,这会给现有补丁的合并带来困难。C99 特 性仅限于新增或需要改动的代码。 注释 Vim 传统上使用 /* 注释 */。我们打算继续在文件和函数头部,还有大块代码处使用这 种风格,如: /* * The "foo" argument does something useful. * Return OK or FAIL. */ 新代码或需要改动的代码上建议使用 // 注释,特别是在代码之后的注释: int some_var; // 这里可用单行注释 枚举 枚举的最后一项可有拖尾的逗号。C89 过去不允许。 类型 允许使用 "long long",并应假定为 64 位。printf 格式中对应可用 %lld。另外,%llu 可用于 "long long unsigned"。 声明 MSVC 2015 是最低支持的编译器,因而声明不需要在块的开始处。但这通常是个好习惯。 建议在循环内声明 for 循环变量: for (int i = 0; i < len; ++i) 这种用法优势明显,所以我们会更经常使用。 不使用 不使用以下 C99 特性,因为支持的编译器尚不广泛: - 可变长度数组 (即便在 C11 中也是一个可选的特性)。 - _Bool 和 _Complex 类型。 - "inline" (也几乎用不到,让优化器自己去做吧) - 灵活数组成员 (Flexible Array Member): HP-UX C 编译器不支持 (John Marriott) 普 通 函 数 的 使 用 style-functions 一些普遍使用的函数都有一个特殊的 Vim 版本。它们的引入是有原因的,因此,一般应 考虑使用这些 Vim 版本。 通用名 VIM 函数名 VIM 版本的不同 free() vim_free() 检查释放 NULL 的情况 malloc() alloc() 检查内存不足的情况 malloc() lalloc() 类似于 alloc(),但是支持长参数 strcpy() STRCPY() 对于 char_u* 类型的参数,会自动将其转换为 (char*) 类型 strchr() vim_strchr() 接受特殊字符 strrchr() vim_strrchr() 接受特殊字符 isspace() vim_isspace() 可以处理 ASCII 码 > 128 的字符 iswhite() vim_iswhite() 仅对 Tab 和空格返回真值 memcpy() mch_memmove() 能处理范围重叠的复制 bcopy() mch_memmove() 处理重叠的复制 memset() vim_memset() 对所有系统通用 名 称 style-names 函数名不能超过 31 个字符的长度 (因为 VMS 的缘故)。 不要使用 "delete" 或 "this" 作为变量名称,C++ 不喜欢这样。 因为 Vim 需要在尽可能多的平台上运行,所以我们需要避免那些已经被系统定义过的名 称。这里是一个已知会造成麻烦的名称列表 (使用正则表达式模式给出)。 is.*() POSIX,ctype.h to.*() POSIX,ctype.h d_.* POSIX,dirent.h l_.* POSIX,fcntl.h gr_.* POSIX,grp.h pw_.* POSIX,pwd.h sa_.* POSIX,signal.h mem.* POSIX,string.h str.* POSIX,string.h wcs.* POSIX,string.h st_.* POSIX,stat.h tms_.* POSIX,times.h tm_.* POSIX,time.h c_.* POSIX,termios.h MAX.* POSIX,limits.h __.* POSIX,system _[A-Z].* POSIX,system E[A-Z0-9]* POSIX,errno.h .*_t POSIX,用于 typedefs。请用 .*_T 代替。 wait 不要使用它作为函数的参数,和 types.h 冲突 index 屏蔽全局声明 time 屏蔽全局声明 new C++ 保留关键字 clear Mac curses.h echo Mac curses.h instr Mac curses.h meta Mac curses.h newwin Mac curses.h nl Mac curses.h overwrite Mac curses.h refresh Mac curses.h scroll Mac curses.h typeahead Mac curses.h basename() GNU 串函数 dirname() GNU 串函数 get_env_value() Linux 系统函数 杂 项 style-various 类型定义 (typedef) 的名称应该以 "_T" 结尾: typedef int some_T; 宏 (define) 的名称应该全部是大写: #define SOME_THING 特性 (feature) 应该总是以 "FEAT_" 开头: #define FEAT_FOO 不要使用 '\"',一些编译器不能处理它。用 '"' 比较合适。 不要使用: #if HAVE_SOME 一些编译器不能处理它并报告说 "HAVE_SOME" 没有被定义。 请使用 #ifdef HAVE_SOME 或 #if defined(HAVE_SOME) 风 格 style-examples 一般原则: 每行一条语句。 错误: if (cond) a = 1; 正确: if (cond) a = 1; 错误: while (cond); 正确: while (cond) ; 错误: do a = 1; while (cond); 正确: do a = 1; while (cond); 错误: if (cond) { cmd; cmd; } else { cmd; cmd; } 正确: if (cond) { cmd; cmd; } else { cmd; cmd; } 单行的块可以省略花括号。if/else 一个单行的块有括号时,另一个块最好也有括号: 正确: if (cond) cmd; else cmd; 正确: if (cond) { cmd; } else { cmd; cmd; } 使用 ANSI (新风格) 函数声明,返回类型在单独缩进的一行上。 错误: int function_name(int arg1, int arg2) 正确: /* * 解释函数的功能。 * * 解释返回值。 */ int function_name( int arg1, // arg1 的简短注释 int arg2) // arg2 的简短注释 { int local; // local 的注释 local = arg1 * arg2; 空 格 和 标 点 style-spaces 不要在函数名和括号间留空格: 错误: func (arg); 正确: func(arg); 但请在 if,while,switch 等之后留一个空格: 错误: if(arg) for(;;) 正确: if (arg) for (;;) 在逗号和分号后留一个空格: 错误: func(arg1,arg2); for (i = 0;i < 2;++i) 正确: func(arg1, arg2); for (i = 0; i < 2; ++i) 在 '=','+','/' 等的前后各留一个空格: 错误: var=a*5; 正确: var = a * 5; 一般地: 使用空行来把代码分组。在每组代码的上面写上注释,这样就可以快速的知道这 些代码是用来干什么的。 正确: /* Prepare for building the table. */ get_first_item(); table_idx = 0; /* Build the table */ while (has_item()) table[table_idx++] = next_item(); /* Finish up. */ cleanup_items(); generate_hash(table);

3. 设计决定 design-decisions

折叠 对同一个缓冲区可以有多种折叠形式。例如,一个窗口显示函数体折叠后的文本,另一个 窗口显示函数体。 折叠是一种显示文本的方式。它不应该改变文本自身。所以折叠是被当作缓冲区文本 (缓 冲区行) 和窗口内显示的文本 (逻辑行) 之间的过滤器来实现的。 为窗口命名 我们一般用单词 "窗口" 来称呼很多东西: 屏幕上的一个窗口、xterm 窗口、Vim 中一个 用来查看缓冲区内容的窗口。 为避免混淆,我们给其他有时被称作窗口的项目起了其他的名称。这些是相关项目的概 述: screen 整个屏幕。对于 GUI 来说,就是类似于 1024x768 像素之类的东西。 Vim 外壳可以使用整个屏幕 (例如,在控制台上运行时) 或它的一部分 (xterm 或 GUI)。 shell Vim 这个应用程序。它可以覆盖整个屏幕 (例如,在控制台上运行时) 或它的一部分 (xterm 或 GUI)。 window 用来查看缓冲区内容的地方。Vim 中可以包括很多窗口,还有命令行、 菜单栏、工具栏等。它们被整合到外壳中。 拼写检查 develop-spell 当要把拼写检查加进 Vim 时,考察了一些可用的拼写检查库和程序。不幸的是,没有一 个能提供足够的能力使其能够作为 Vim 的拼写检查引擎,原因很多: - 缺乏对多字节编码的支持。至少要支持 UTF-8,以便同一文件能使用多种语言。 实时转换未必总是可行的 (这需要 iconv 支持)。 - 对于那些程序和库: 如果不作任何改动,需要和 Vim 分开单独安装才能使用。一般来 说,这不是不可以,但总是个问题。 - 性能: 测试表明这些程序和库可以像语法高亮那样支持 (重画时) 实时的拼写检查,但 那些代码所用的机制太慢了。例如使用哈希表的 Myspell。大部分的拼写检查程序使用 字缀压缩 (affix compression) 算法也会使其速度减慢不少。 - 使用像 aspell 这样的外部程序需要设置通信机制。这会使可移植性变得复杂 (只考虑 Unix 系统相对简单点,但还远远不够)。而且性能也是个问题 (大量的进程切换)。 - 缺乏对包含非单词字符的单词的支持,如 "Etten-Leur" 和 "et al."。需要把词中每 个部分都标识为好词,但这样就降低了检测的可靠性。 - 缺乏对区域或方言的支持。例如,接受所有英语单词并且单独高亮出非加拿大的单词是 很困难的。 - 缺乏对生僻词的支持。有很多词拼写正确但几乎很少使用,却有可能是拼写错误的常用 词。 - 对于拼写建议,速度不太重要,要安装其他的程序或者库也是可以接受的。但是这些程 序或库提供的单词列表也许与拼写检查程序使用的不一致,给出的建议可能是拼写检查 程序检测出的拼写错误的单词。 拼写建议 develop-spell-suggestions 对于实现拼写建议,有两种基本的方法: 1. 尝试轻微改变错误的单词,然后检查能否和正确的单词匹配。或者浏览正确单词列 表,轻微改变它们,看能否和错误的单词匹配。这里所指的改动包括: 删除一个字 符,插入一个字符,交换两个字符等。 2. 比较坏词和好词列表的发音来寻找匹配。可能也需要像第一种方法那样对单词做少许 改变。 第一种方法的优势在于查找录入错误。在使用哈希表试验和参考了其他拼写检查程序的方 案后,得出结论: trie (一种树结构) 是理想方案。既可以减少内存占用,又可以尝试合 理的改变。例如,只有在能够拼成正确单词的情况下才插入字符。其他方式 (使用哈希 表) 需要在单词的所有位置尝试所有可能的字符。而且,哈希表需要单独标识单词的边 界。而 trie 就不需要这样做。这样会简化很多。 当我们知道单词如何发音却不知道如何拼写时,按发音折叠匹配就很有用了。例如,单词 "dictionary" 可能写成 "daktonerie"。第一种方法需要尝试的次数非常多,很难找到正 确的单词。按发音匹配后,这两个单词变成了 "tkxnry" 和 "tktnr",两者仅有两个字符 的差异。 为了通过相似的发音来查找单词 (寻找按发音折叠等价的单词),我们需要所有发音相似 的单词列表。为此,我们作了一些试验来寻找最好的方法。可供选择的方案是: 1. 在寻找拼写建议时,实时进行发音匹配。这意味着浏览 trie 树以寻找正确单词,匹 配每个单词发音,并检查它与错误单词的不同。这种方式内存占用少,但花费时间 多。在一台比较快的电脑上,对于英文需要几秒,这对于交互式使用还可以接受。但 是对于某些语言,则超过 10 秒 (如: 德语、加泰罗尼亚语),这就慢得无法忍受了。 对于成批处理 (自动更正),所有的语言处理起来都很慢。 2. 把 trie 用于发音匹配的单词,这样查找时就可以和没有用发音折叠的方法一模一 样。但这需要对每个相近的发音记住所有好词的列表。这种方式搜索匹配很快,但是 要占用大量内存,其级别大约在 1M 到 10M 之间。对于某些语言,甚至会大于原单词 列表。 3. 类似于第二种方案,但用字缀压缩 (affix compression) 并且只保存发音相近的单词 基本部分 (字根) 来减少内存开销。aspell 就是这么做的。缺点是,在按发音进行匹 配前,需要从错误的单词上分开字缀,这意味着在单词头和/或尾出现的错误会导致本 方案失败。而且当错误的单词和正确的单词间有很大不同时,本方案会变得很慢。 我们作出的选择是使用第二种方案,并使用单独的文件。这样,有足够内存的用户可以得 到很好的拼写建议,而内存不足或者只想要拼写检查而不需要建议的用户就可以不用那么 多内存。 单词频率 对建议进行排序时,知道单词常见与否很有帮助。理论上,我们可以在字典中对应单词保 存一个单词频率。但是每个单词都需要计数。这会使单词树的压缩效率大为降低。而且对 所有的语言都要维护单词频率,将是个繁重的任务。另外,最好优先考虑文本里已经出现 的单词。这样在特定文本里出现的单词可以被建议列表优先列出。 现已实现的是统计已经显示的单词。哈希表用于快速查找单词的计数。字缀文件的 COMMON 项目中列出的单词会先有初始计数,这样,即使新编辑文件时也能马上用此功 能。 这还不够理想,因为 Vim 运行的时间越长,单词计数就会越大。但实际应用中,这仍然 比不使用单词计数的情形要改进不少。

4. 假定 design-assumptions

变量的大小: char 8 位有符号数 char_u 8 位无符号数 int 32 或 64 位有符号数 (16 位是可能的,但功能会有限制) unsigned 32 或 64 位无符号数 (16 位情况同 int) long 32 或 64 位有符号数,可具有指针 注意: 某些编译器不能处理过长的源码行或者字符串。C89 标准规定限制在 509 个字符 以内。 vim:tw=78:ts=8:noet:ft=help:norl: