我想分享一下我的emacs配置和学习思路,并向大家介绍一些我觉得有用的package

自子龙山人的优秀教程进入emacs的世界, 一路使用和阅读过很多优秀的emacs配置,spaceman’sdoom emacscentaur emacslazycat-emacsredguardtoo emacsDaviwil emacs等,每次都像是进入了一个新世界。

随着对emacs越发地理解和熟悉,我发现想使emacs配合自己的工作达到如臂指使的地步,就一定要深入了解自己的配置结构。不仅仅是这些代码配置了什么这种事情,而是清楚地掌握这些代码的低抽象层级和调用流程。

因此在从上述优秀代码吸取了大量知识的基础上,出现了我自己的emacs 配置,也是我的工作和生活解决方案: nowisemacs,希望能对也尝试自己攥配置的同学有一些帮助。

nowisemacs试图解决我遇到的这几个问题:

  1. 代码抽象程度太高,一旦遇到问题不知道如何排查和解决
  2. 现有的feature不能满足需要,经常想要拓展新的功能,但一改又影响了别的地方
  3. 装了大量同类型的包却有些混乱,甚至难以熟悉
  4. hack太多则难以后续维护,hack太少却又不知道怎么去满足需求,换而言之,难以控制默认配置的复杂度
  5. 代码洁癖,不想看到自己不用的代码或者包,但因为缺乏对整体的把控,删除后影响正常使用。

我想先给出我度过这几个初学者困难的思路历程:

  1. 在一些成熟的通用性配置doom emacs等使用了很长时间,这段时间我主要熟悉了emacs的各种或酷炫或实用的功能,这极大地引起了兴趣并且明确了一份优秀的emacs应该是什么样的
  2. 尝试自己写配置,但很快因为不知道如何梳理自己的配置流程被阻塞
  3. 找了一份非常native 的配置,lazycat-emacs,学习如何用最底层的方法配置所有的代码,包括手动管理包,手动控制加载顺序等
  4. 理解了emacs在启动后需要哪些东西之后。我需要搞清楚的事情变成了两个:第一,实现这些功能,第二,控制配置的抽象等级以不提高维护压力

nowisemacs的配置并不是一下子就有的。有一篇文章《从操作系统架构的角度理解emacs》给了我很好的启发,让我尝试从一个emacs本身而不是emacs用户的角度去使用emacs。作为一个emacs,我该如何满足用户需求呢?

第一,当用户给我需求的时候,我需要知道怎么组合现有的功能去实现这个需求;第二,我能找到或者实现这些功能。

有了这个思路,nowisemacs尝试分离功能结构和代码结构来解决这个问题:

  1. 代码结构。nowisemacs也采用了平铺式的代码结构,也就是尽可能不使用包的嵌套或者跳跃配置,包的加载顺序是顺序执行的。这种方法最大的优点就是容易排错和维护。但平铺结构的一大问题就是难以对代码进行不同需求等级的把控,容易存在明明有一些功能却找不到。
  2. 功能结构。这里是为平铺带来高角度的视角。nowisemacs会优先复用emacs自带的包或者功能,这些代码往往功能单一性强且实现简洁,彼此之间配合起来浑然天成。在这个基础上,nowisemacs 采用org mode大纲式管理所有的功能需求,结构是从代码出发的,但却以功能为层层分类,这样有需求时可以迅速找到可用的功能。
  3. 代码抽象层级。nowisemacs尝试保证看到任何一行配置代码时,都和基础代码一样清晰。因此放弃了容易管理但底层难以理解的use-package,经过一段时间使用也放弃了代码清晰但管理较为重复的手动配置,最终使用了setup这个包。setup的主要缺点是功能较少,但极易的拓展性和随时可以expand成“基础配置”的高可读性是其成为了理想选择。
  4. 包复杂性控制。在选择包时会尽量选择依赖少的、实现思路简洁的、需要用户配置尽可能少的包,这样的包意味着配置简单且耦合性低,完全可以作为某个子领域的独立解决方案,甚至无需配置快捷键,仅通过M-x在需要的时候搜索相关函数即可。这也意味着nowisemacs的选择是激进的,即不存在向后兼容,代码和包若用途较小则会被去除。

其实我的配置没有太多值得称赞的地方,我想和大家分享的是我的配置思考和沉淀下来的理念,希望能给其他曾经和我一样望着emacs代码高山却不知道如何迈步的用户一点借鉴的意义。nowisemacs是我尝试这种方式的一个实现,欢迎尝试里面的配置或者提issue~~

其实真的蛮感谢emacs china的各位和其他喜欢emacs乐于帮助的人,为新人熟悉和理解emacs起到了至关重要的作用,是领路人和引导者。对我个人而言,我受到了很多帮助和启发,这甚至改变了我的科研、工作和生活的主要方式。

40 个赞

实时语法检查/纠正 lsp-ltex 或者 eglot-ltex

language tool 是一个支持20多种语言的免费语法检查软件。作为lsp的language tool emacs客户端,这两个包相比之前我用的emacs-langtool等有着如下优点:

  1. 非阻塞性,实时检查不卡顿。
  2. 以各种标记提示用户哪些地方的单词或者书写存在问题,并支持多种方法查看建议和一键修改。 Untitled

如图一个简单实例,eglot提示noo存在问题,鼠标悬停发现是:possible spell error,单击后提示了几种解决方案,选择no后自动完成了纠正。

ltex还支持侧边栏查看所有错误等功能,对于英文写作是非常有用的

3 个赞

popper,统一管理各种临时的emacs弹出窗口

Emacs 的各种窗口弹出非常恼人,经常出现临时查看一些东西,关闭却破坏了先前的窗口结构。Popper通过多种方式统一设置指定mode/buffer,并控制弹出方式,确保这些窗口在使用后会无痕消失,不破坏结构。

进一步的,还只是一键弹出先前临时窗口和遍历历史临时窗口的功能。用起来真的爱不释手

1 个赞

Dirvish, a minimalistic file manager based on Dired .

这个包是一个类似于ranger的emacs文件浏览器。主要有这几个优点:

  1. 和dired无缝集成,因此可以直接使用dired的近乎所有配置,包括dired-subtree等第三方包,dirvish自己只需要简单配置,默认在用户直接调用dired相关命令的时候会无缝衔接为dirvish。切换到dirvish后甚至可以像正常使用dired那样使用
  2. 异步处理和渲染,所有预览操作均不会阻塞emacs,甚至路径下有大量文件也不会像dired那样卡住,非常丝滑
  3. 自带图标,不用安装all-icons dired包,不要考虑对齐问题 下面看一下效果
7 个赞

好奇,如果 setup 内的代码有错误,setup 是会 ignore,warning,还是抛出错误,阻止加载啊?

dtache 方便的分离和管理 shell 命令

这个包在elpa里,通过dtach来实现类似于screen的一些功能。主页分享了详细的说明文档和youtube的演示链接。

主要实用的地方如下:

  1. detach。emacs进程被kill了也能继续运行,以后还可以连接过来,特别适合不稳定的远程连接等
  2. 管理这些命令。已经运行的命令可以很方便地拿到运行过程/结果的buffer,可以随意选择随意浏览,随意重新运行等等,因为这些buffer都是文本信息,因此detach还支持diff和copy等操作。
  3. 如果你和我一样是embark的用户,很灵活的可以选择和更改操作。detach对embark的支持很好
  4. detach提供了大量和用户执行命令相关的元数据,包括时间、运行状态等,还可以把这个session存储为文件以永久化。

在我个人的使用经验中,dtache特别适合对需要较长时间运行的命令进行管理和整合,方便的多次运行和详细的控制信息是我可以放心的进行各种操作。

setup的代码就是一堆宏,换句话讲。setup在emacs启动并运行这些代码的时候就消失了,emacs看到的是原生的配置信息。我举个例子:

我们配置一个package的时候可以进行结构化的配置,这是我们写下来的有层次的代码,

  (setup diredfl
    (:hook dirvish)
    (:autoload test-command)
    (:bind "C-c h" test-command)
    (:with-map dired-mode-map
      (:bind "TAB" dired-subtree-cycle)))

当运行时,宏会首先被展开成下面的代码:

  (progn
    (add-hook 'diredfl-mode-hook #'dirvish)
    (progn
      (autoload 'test-command "diredfl" nil t))
    (eval-after-load 'diredfl
      #'(lambda nil
          (define-key diredfl-mode-map "h" #'test-command)))
    (eval-after-load 'diredfl
      #'(lambda nil
          (define-key dired-mode-map "	" #'dired-subtree-cycle))))

接着emacs才会执行这些代码。

因此,如果错误来源于这个宏的不对,那么emacs会直接报出宏展开的错误,如果是包的配置不对,才会报出功能性的error。这两者均会直接中断运行。

但如果这个宏包含了异常处理,那么就会进行处理后正确运行。(也就是说,用了setup配置,和直接手动配置没什么运行上的区别,都是用户写了什么样的流程就怎么做,没有复杂的机制或者黑盒子)错误处理不是来源于setup,而是用户

我知道是宏…我想要的只是当我这个区块的配置出问题时,不影响其他配置加载

看起来没有leaf的这个功能…(

哦哦,我理解你的意思了,你提出的是一种类似于try except的代码。setup默认只有很简单的一些宏,这种很实用的额外功能只能用户自己写,它确实没有这种处理办法。感觉我可以去leaf那边看能不能把这个保护机制搞过来,加在一些容易出问题的配置上。感谢你的信息 :joy:

1 个赞

我的使用经验:

  1. 自己编写插件,代码需要高度抽象和简洁,为了避免重复代码需要编写一下简单的 macro
  2. 配置代码最主要的目标有三个:扁平化易维护、启动插件尽量少、按键触发新插件加载
  3. 没事别更新,用Git Submodule控制住最稳定的插件版本组合,能工作就别天天更新,稳定环境干活最重要

相对于上面我的经验,我在社区看到的一些比较有趣的现象:

  1. 对外分享的插件代码写的不够干净,代码量大了以后自己维护都很困难
  2. 配置代码过度封装,出了问题不容易调试,当需要对一些插件进行高级微调,反而受限于代码封装和包管理插件的框架限制,配置代码,最朴素的写出来能够理解所有配置的意思就行了,没有必要在配置文件上雕花
  3. 更新频繁,特别喜欢很多插件一起更新,更新挂了后,每天没时间干活,都在折腾版本升级

大家都习惯搜集一些高级插件,我的使用经验是,不管插件功能再强大,只要使用频率低或者用着心智负担太大的插件果断删除,真正天天用的插件没几个。

29 个赞

深表赞同。。

这个有没有不依赖鼠标的解决方案?我觉得 ivy-flycheck 那样就很好

lsp-ltes不清楚,但eglot-ltes可以不用鼠标,光标移动到单词上会自动弹出一样的提示,执行code-action会在minibuffer🀄️弹出选项,和用鼠标没什么区别

1 个赞

:+1: 给你点个赞!

2 个赞

谢谢分享,学习下 :grinning:

1 个赞

如果只是恢复窗口布局,winner-mode会不会轻量一点。。?

看到你用的也是corfu,不知道你在使用clangd作为语言服务器时有没有无法正确补全snippet的情况?

如果是从轻量级的角度,那确实是winner-mode好,毕竟内置了。但如果从管理临时窗口的实用和省心的角度,popper优秀太多。我两者都用,这两者在我看来是不同的目的,popper的作用非常单一化,winner的用途反而更广泛一些,我认为前者是后者在细分领域的强化,因此混用也很方便

很多时候,用户只是想临时查看一下文档,或者打开一些buffer临时看一下,还有可能是运行一些命令待会再看一下。这些操作都可能会多重复几次,比如看了一些buffer内容后发现需要再看一些新的buffer。

起初你在buffer a,b工作工作,然后突然有了临时需求,因为emacs不同mode的窗口弹出十分混乱,有的是在下面临时弹出,有的是复用已经有的buffer,这几种情况只要你稍微多追几次,你原来的工作布局就会全乱掉,buffer a和b甚至都不在你的可视区域了,既不能作为阅读的参考,想回去的时候只能一路winner undo。但这些临时buffer会打乱winner的线性历史,有时回不去了。

popper会把所有的这些临时buffer统一管理,比如在下方开一个临时窗口,所有的buffer都会在这个窗口里,确保不会影响到任何之前的buffer,还可以很方便的遍历历史和随时弹出先前的内容。

这玩意讲起来有点绕口,其实用一下就知道了。但如果感觉winner已经满足了,那继续用winner就好,没必要增加自己用不到的需求,平白增加维护压力😄。

3 个赞

clangd我用起来好像没啥问题啊,我录了个动态图,你看一下我有没有理解对你的问题,是snippet吗? Untitled

持续 buffer 和 临时 buffer 的概念用处挺多的,最著名的就是 which-key 的临时 buffer。

1 个赞