基于 spacemacs 的开发
本文主要记录在开发中使用 spacemacs 作为主要编辑工具的一些心得。
不管使用 IDE 还是编辑器加拓展来编程都绕不开两方面的需求:浏览已有代码和修改编辑新代码。两者对编辑的需求有相同点,但多数还是有差别。
本文不谈如何快速的定位到文件中的某行或者某字符等编辑上的技巧,仅仅讨论使用 spacemacs 这套配置如何应对上面的两类需求。
安装
如果系统中自带的安装包版本过于老旧时,可以自行编译,目前我使用的是 emacs 29 的版本,编译的过程如下:
$ git clone --branch emacs-29 --single-branch git@github.com:emacs-mirrors/emacs.git
$ cd emacs && mkdir build-29
$ ./autogen.sh && cd build-29
$ ../configure \
--prefix=$HOME/.local/ \
--with-native-compilation \
--with-json \
--with-tree-sitter \
--with-imagemagick \
--with-pdumper=yes \
--without-x
$ make install # install
$ make uninstall # uninstall
$ make clean && make distclean
浏览已有代码
对我个人而言,浏览代码常用的功能主要是能快速的定位到如下几点的位置:
- 变量的定义/引用
- 函数的声明/定义/引用
- 项目/文件中的字符
- 项目中的文件
针对上述的 1~2 点,spacemacs 加 lsp 后端可以完全的胜任“定义”和“引用”的查找。且 基于语义的分析,得到的结果往往是更准确的,跟基于 tag 之类的搜索不可同日而语。如下的 命令基本满足上述的两点需求:
- , + g + d 或者 g + d 可以跳转到变量/函数的声明和定义位置;
- , + g + g 在变量/函数声明和定义位置之间跳转;
- , + g + r 可以搜索引用当前变量和函数的所有位置。
我在工作中主要使用 C++/go/python 等语言,对应的 lsp 后端在 spacemacs 中都有对应 的 layer,可以开箱即用。C++ 的后端,我选用的是 ccls,因为它能提供的特性更多,比 如类的继承层次结构等。其他的 lsp 后端通过 SPC + h + l 参考对应的 layer 文 档安装即可。
对于 3~4 点,spacemacs 封装的 helm/ivy 拓展已经可以完全的满足需求:
- SPC + s + s/S 可以快速的在文件中查找想要的字符;
- SPC + s + p/P 可以在项目中定位字符;
- SPC + s + f 在目录中搜索字符;
- SPC + s + d/D 在当前文件所在目录中搜索字符;
- SPC + p + f 可以查找项目中的文件;
- SPC + p + b 可以查看项目中已打开的 buffer;
- SPC + b + b 可以打开最近的文件;
- , + g + a 可以打开关联的头文件或者源文件;
修改编辑新代码
编辑代码主要有两方面:修改和新增。
对修改已有代码而言,常见的操作有局部更新和全局更新。典型的操作给变量重命名,如果是函数体内部的变量即局部更新,如果是全局变量就涉及全局更新。
对于重命名尽量使用基于语义的操作,比如 lsp-rename 。如果没有这样的“函数”功能,针对上述需求,我的工作流程如下所述:
局部编辑
缩小需要编辑的范围,然后使用多行编辑。流程遵循“选择范围”->“区域编辑”->“提交变更”->“恢复范围”的模式进行,具体如下所述:
- 选中需要编辑的行范围,比如函数体内定位到花括号处,然后用 V % / v i { 等 VIM 中的常见选择操作来选中整个函数体,或者使用 SPC v 来一步步扩大选择范围;
- 使用 narrow region 将编辑范围固定,在该 region 内部可以任意的编辑内部的文本也不会影响文件中的其他部分,快捷键是 SPC n r ;如果是想针对函数操作的话,可以直接使用 SPC n f ;
- 在上述范围内,先选中待编辑文本然后按 SPC s e 进行多行编辑;
- 编辑完后通过 SPC n w 恢复整个文件的范围。
通过上述操作即便是某些不能根据语义来修改的文本,也能轻松无副作用的修改。
除却上述小范围的修改,有时需要在整个文件内部做修改,比如改名,希望能将注释中的名字也改掉。这时的操作逻辑略有不同。
- 在文件内部搜索要修改的文本,选中一个文本按 SPC s S 进行文件内部检索;
- 再按 C-c C-e 进一步编辑搜索到的文本结果;
- 按 SPC s e 使用多行编辑进行修改;
- 按 C-x C-s 保存修改并退出编辑。
全局编辑
有时需要修改的文本涉及到项目中的多个文件,上述的方式虽然不在可行,但是思路是一致的:搜索到要修改的文本(不考虑文件的概念),编辑搜索的结果。
具体的操作流程为:
- 选中编辑文本,按 SPC s P 在项目中搜索要编辑的文本;
- 编辑搜索的结果, C-c C-e 进入到编辑页面;
- 需要要编辑的文本,进行多行编辑 SPC s e ;
- 按 C-c C-c 保存修改并退出编辑模式。
通过局部/全局的编辑操作流程,便能满足日常开发中绝大多数的编辑任务。再配合上不同语言的后端,便拥有了绝大多数 IDE 的核心功能了。
多项目开发
在日常的开发中,很大可能是不只涉及到一种编程语言环境,例如很可能需要在 C++ 和 Python 的环境下进行频繁切换。在 spacemacs 的使用过程中发现其对项目的管理已经非常方便易用。
项目的切换可以通过 SPC p p ,浏览最近打开的项目,然后选择打开。如果对应的后端没有及时的生效,可以重启后端, , b r 便会重启后端 server,然后对项目进行重新索引解析。
同时也可以使用 layout 来保持多个项目的窗口布局和 buffer,无需每次重新打开项目。 SPC l 可以浏览跟 layout 相关的所有命令,另外还可以将现有的 layout 持久化保存到文件中,如此即便 emacs 退出,下次只要重新加载该布局文件即可回复所有的工作现场。
相较开多个 emacs 的实例编辑多个项目而言,在一个编辑器中的好处是:可以方便的在不同语言的项目中进行文本的粘贴拷贝,也可以同时开两个 window,对比不同项目的实现。
此外,emacs 还提供 daemon 常驻进程模式,这样即使进入一个新的目录,也可以通过 emacsclient 快速的在一个 emacs 中打开当前的项目,方便多项目的编辑和提升启动体验:
$ emacs -nw --daemon
$ emacscient # could alias the emacsclient as `e`
NOTE
如果打开项目较多,有时 lsp 的后端会占用比较大的系统内存,这时可以适当关闭一些 buffer 来节省内存。只要通过 SPC b I 即可看到所有项目打开的所有 buffer,通过 d 标记关闭,然后按下 x 键来关闭标记的 buffers。
vterm
spacemacs 中可以通过 SPC ' 和 SPC p ' 唤出 terminal 来执行一些临时 shell 命令, 默认的 ansi-term 使用中有诸多不便,按照官方推荐的 shell layer 中说明, 配置 vterm 会好用很多。同时也可以避免在 shell 中粘贴文本时 Emacs 进程 hang 住的问题。
在 vterm 中操作时,有两个操作需要注意。不同于 terminal 中的 C-c 操作,在 vterm 中是 C-c C-c。如果想要结束 vterm,可以通过 C-d 退出,这个跟 terminal 中是一致的。
不足
经过一年的服务端开发,spacemacs 已经成为每天重度使用的编码工具,现在时常还能发现一些以前未知的小惊喜, 比如后面罗列的一些功能快捷键列表。 整体而言对 spacemacs 已经比较满意。
如果非要提点不足,那就是日常使用中的某些操作还未能发现 spacemacs 中如何实现,比如向前跳转到光标位置,在 VIM 中 C i 是 jump-forward, C o 是 jump-backward。但是 spacemacs 中的 jump-forward 却始终不能工作。
jump-forward的问题是 terminal 下才会发生的状况,主要原因是 terminal 不能区分 C-i 和 TAB
- iTerm2: 可以通过如下的按键映射来解决;
-
wezterm: 在
wezterm.lua配置中添加如下的部分:config.keys = { { key = 'i', mods = 'CTRL', action = wezterm.action.SendString '\x1b[10' }, }在 spacemacs 的
dotspacemacs/user-config ()中添加下面的按键映射:(global-set-key (kbd "M-[ 1 0") 'better-jumper-jump-forward)
再有就是在终端环境下,有些主题的配色不是很好,像选择了 doom-one 主题后, magit 文件 diff 时的配色就比较突兀。
瑕不掩瑜,总体而言 spacemacs 对 VIM 用户而言绝对是一套相当顺手的配置。感谢 EMACS 社区贡献!
最后
虽然 spacemacs 的 develop 分支开发活跃,但是建议阶段性升级,除了偶尔的不兼容外,有时能获得意想不到的收获。比如最近将一年前的 develop 分支升级到最新版本,发现在文本编辑时的性能和流畅度有了质的提升。当然升级前还是要及时的做好备份。
快捷键列表
- SPC r y 对粘贴历史的再次引用,
- SPC o s 是打开 yasnippet,
- z c/o/m/r 折叠代码块,
- SPC w u 恢复之前的窗口布局,
- SPC t g 自动黄金比例分割窗口,
- SPC c c/C/n 编译和查看错误位置,
- SPC e . 可以在文件中的 flycheck 检查的错误之间跳转,
- SPC j l 快速在行之间跳转,
- SPC j w SYM LOC 在当前 buffer 的单词之间跳转,比 SPC j j 字符之间跳转效率高,
- SPC j f 可以查找 Emacs 中的函数定义,
- SPC s l 恢复最后一次的搜索,
- SPC s f Search in directory,
- SPC r l 恢复上一次 minibuffer 的搜索,
- g r q 去掉多光标编辑(emc/evil-mc-undo-all-cursors),
- SPC S s 单词拼写检查,
- SPC s e F/L 限制 iedit 在函数内部和单行,省掉 narrow;
- SPC m h h / , h h 查找当前光标下的单词的所有信息,
- , = = 代码格式化,
- SPC t TAB 高亮显示缩进列,
- SPC h M 切换 Major mode,
- SPC h l 本地查看各自 layer 的 README,
- SPC h r 本地查看 spacemacs 的文档,
- m SYM / ' SYM 在一行打 marker 并快速回到刚才的 marker 位置,
- C-z 在 mini-buffer 呼出 helm-action 的功能,
- SPC f v 定制文件或者目录的一些局部变量。