PHPCD六周年随想

2021-08-12 ⏳5.2分钟(2.1千字) 🕸️

PHPCD是我维护的一个 vim 插件,自从在 2018 年转 Go 语言开发后,就不怎么开发了。如果有严重的 BUG 可能会修一下。这两天有人提新的 issue。我看了一下问题列表,好多 issue 都是 18/19 年提的。大多数问题也不复杂,完全可以在当时就说明白,可我什么也没说。作为项目的发起人,我有一种淡淡的伤感和羞愧,就仿佛是自己遗弃了自己的孩子。怀着这样的一种歉意,我仔细看了这些遗留的问题并逐一调试和反馈。其实,PHPCD这个插件是在 2015 年构思并实现的,到现在已经六年了。今年我就跟大家讲一下它的故事。

PHP是世界上最好的语言🐶,也是我职业生涯中的第一门语言。刚毕业的时候自然要用IDE开发PHP。当时有两种选择,一个是 Eclipse + PDT(PHP Developer Tools) ,另一个是 Zend Studio。其实那个已经有 PHPStorm 了。PHPStorm 1.x 是在 2010 年发布的。那个时候在国内基本没人知道。我自然是不希望用盗版的 Zend Studio,所以选择了 Eclipse 方案。到现在我还依稀记得在 Eclipse 上配置 Xdebug 实现单步调试 PHP 代码的那种喜悦。

我从 2013 年开始就转向 vim 编辑器。甚至可以说是中了 vim 的毒,希望所有的工作编程语言都能在 vim 下开发。其实这种转变也非常偶然。当时我入职一家小公司,所有人都有 vim 开改 python,而且给到每一个人的 vim 配置都非常方便。后来我才弄明白,最关键的 vim 插件是 jedi(用于 python 补全和跳转)和 CtrlP( 用于文件模糊搜索)。我一直推崇小而美,所以我对当时的 vim 作了裁剪,最终形成了自己的 vim 配置体系

好了,继续说 PHP 开发。讲到编程,最常用的可能就是代码补全和跳转了。我从网上找到了 phpcomplete 这个项目。这是一个很厉害的插件。说他厉害,是因为他能够「理解」PHP代码的语义。比如说我们要在这里补全$this->||表示光标所在的位置),phpcomplete 会发现这需要查找 $this 变量成员,而 $this 表示当前 class,phpcompete 会扫描当前文件确定当成的 class 名字。这些补全和跳转所用到的信息是通过 ctags 生成的 tags 文件提供的。

文件改动后需要重新生成 tags 文件,而且对于复杂的项目,这个过程会比较耗时。

我在想能不能不生成 tags 文件呢?想来想去,在 2015 年的某一天,我想到了一个办法,反射。PHP 内置反射功能,可以查询某个类有哪些成员变量和成员方法,可以查询某个方法或者函数的出入参信息,还可以查询某个类或者方法在源文件中的位置。这为替换 tags 文件提供了可能。

这个想法💡一直在我脑海里转悠。所以我也就一直在试着完善这个方案。最终的方案是这样的:一部分用于确认需要补全或者跳转的符号的类型(对象或者函数),这上部分基于现有的 phpcomplete 功能;另一部分基于 PHP 的反射实现,用于查询类成员信息和函数参数信息。

这个方案还有一个问题需要解决,就是两部分如何进行通信。phpcomplete 的部分是用 VimL 实现的,在 vim 进程运行。反射部分是用 PHP 开发的,需要在 PHP 进程运行。两者如何实现跨进程通信呢?这就得不得不提到 neovim 这个项目了。

大约是在 2014 年,巴西🇧🇷人 Thiago de Arruda Padilha 试着给 vim 社区提交异步事件和任务控制功能,没有被社区接受。可 Thiago 另起炉灶,发起了 neovim 项目,还在网上发起募捐,最终筹得三万多美元💵,neovim 项目顺利开张。

neovim 最吸引我的地方就是支持 rpc 功能。简单来说,neovim 可以启动任何后台任务,并通过 msgpack-rpc 与之进行通信(通过网络或者管道)。这正好可以解决跨进程通信的问题。于是 PHPCD 诞生了。PHPCD 的全称是 PHP Completion Daemon,这个后台进程本身是用 PHP 开发的。

我先是写了一个简单的原型,来验证我的想法。验证能跑通之后,就开始了快乐地编程之旅。那当时我上班乘地铁🚇,上车站是始发后的第二站,每次都有座位💺,我就抱着电脑在地铁上调试。每次二三十分钟,不知不觉就到站了。等 PHPCD 功能基本完善之后,我就一直用它开发 PHP 代码了。用 PHPCD 来开发 PHPCD 也是一段很有意思的经历。因为 vim 要启动 PHPCD,而 PHPCD 在运行的时候已经加载了相关的代码,所以修改 PHPCD 的代码后不能实现补全和跳转。

开发 PHPCD 还有一个初衷,是希望有更多 PHP 开发者能参与插件的开发。在 neovim 之前,vim 的插件大多是由 VimL 开发的。这是一种非常小众的脚本语言,语法很奇怪,参考资料💾也很少,所以 vim 插件开发的门槛是很高的。而 phpcomplete 的核心逻辑是由大多三千行 VimL 代码实现的,普通 PHP 开发者根本不愿意去学习。我的想法是把尽量多的逻辑用 PHP 实现,逐步减少 VimL 的占比,降低插件的开发门槛。其实我最早的设想是完全用 PHP 重写 phpcomplete 的功能,但到现在还没有实现。最终 PHPCD 的 VimL 代码被缩减到了一千行多一点。最初的愿望并没有达成,实际参与插件的 PHP 开发者也寥寥无几。

到了 2016 年,微软发布了 language server protocol,试图解决针对不周编辑器需要重复开发语言相关功能的问题。事实证明,微软的思路成功了。现在的主流语言都有对应的 language server,像 go 语言更是有官方维护的 gopls。因为有了 language server,不同编辑器之前、甚至是编辑器与IDE之间的功能差异也越来越小。

PHPCD 从某种程度上讲也是一种 language server,但因为有大量的逻辑基于 phpcomplete 的 VimL 实现,所以没法被其他编辑器使用。另外,PHPCD 没有索引,也就没法实现根据接口查实现、根据基类查子类、根据类名查引用这一类的操作。因为不能查找引用,重命名这样的功能也就无法实现。最终,PHPCD 被 language server 抛到后面。

到了 2018 年,我完全转向了 go 语言开发,更没有精力维护 PHPCD,也就有了文章开头的故事。六年过去了。PHPCD 从问题到想法,最终被开发出来,而且陪伴我走过很长的一段职业生涯。这种用代码实现想法的体验让我有了一种足以改变世界的自信,我相信这也是编程最大的魅力。