如何快速使用 ELisp 进行插件编写

最近踩过的坑,另外想看看大家当初是怎么从 ELisp stackoverflow 的水位到能写插件的水平?

2 个赞

感谢大佬分享的 GitHub - chrisdone/elisp-guide: A quick guide to Emacs Lisp programming ,对新人挺有帮助 :smile:
我才知道 C-M-xC-x C-e 好用。

3 个赞

我开始写自己的配置的时候,刚好用了新出的一批插件。为了我自己用得爽,给它们提交了不少 PR,包括 awesome-tab、awesome-pair、awesome-tray(懒猫全家桶 :rofl: )、selectrum、ctrlf。通过读它们的代码,还有作者的 code review,我熟悉了很多 Emacs 内置的 API 还有 elisp 的惯用写法。另外自己也写了一些编辑文本用的命令。这一段经历起了很好的打基础作用。

大概搞了半年以后,我发现现有的 ctags 插件都不能让我满意,就开始写 Citre 了(结果 Citre 都写好了我自己的配置还没完工 :rofl: )。在写的过程中我发现:

  • 自己造一个工具,比和 Emacs 现有工具整合更容易。xref 已经是比较整洁的了,Emacs 的补全机制才是一团浆糊,幸好我搞 selectrum 的时候已经踩过一遍坑了。这种时候上网查查「怎样写一个 minor-mode/补全后端/imenu 后端/等等」,或者看看现有插件的实现是很有帮助的。

  • 保证插件在生长到一定规模以后还可以扩展,比把功能写对更难。Citre 长到快 2000 行的时候,ctags 的开发者及时建议我把代码分层,这起到了关键作用。不然的话 Citre 不会发展到今天这个规模。

其实有一颗造轮子的心,然后干就完了,不需要想太多。

看到楼主的文章里说一开始没有搞懂「position」是什么东西,其实这个写多了就条件反射了。一个 buffer 最开始是 1,最后面是 (buffer-size),narrow 以后最开始是 (point-min),最后面是 (point-max),光标是 (point)。我觉得楼主这个阶段的话,可以先写一些给自己用的编辑命令,我有几个点子供参考:

  • 可不可以写一个回到行首的命令,当光标在行中间的时候回到行首,在行首的时候跳过这一行开头的空白?同样还可以写一个类似的跳到行尾的命令。

  • 有时候需要删掉一串连着的空格,这个倒是有 hungry-delete 可以用,但有时候又不想按一下退格就把空格都删了,这种时候 hungry-delete 就很烦。可不可以写一个命令,当光标至少有一侧是空格时,把两边的空格都干掉;当两侧都没有空格时,插入一个空格?

  • Emacs 内置的 tabify 和 untabify 很好用,但没有选区的时候就不工作。可不可以写个命令包装一下,让它们在没有选取的时候操作整个 buffer?

15 个赞

我是通过维护[quote=“kinono, post:3, topic:17815”] Citre 长到快 2000 行的时候,ctags 的开发者及时建议我把代码分层,这起到了关键作用。 [/quote]

确实,代码量一大,没有很好的结构,后期更改太痛苦了,这是我重构 pyim 时的直观感受。。。 但没有痛苦就没有成长, 有时候很无奈啊。

2 个赞

@alphapapa 的这篇开发手册也值得一看: GitHub - alphapapa/emacs-package-dev-handbook: An Emacs package development handbook. Built with Emacs, by Emacs package developers, for Emacs package developers.

内容详实,除了 Emacs/Elisp 本身的各种议题之外,它还介绍了测试&打包框架类库以及其它辅助开发的工具(例如录屏等等):

  • Animations / Screencasts
  • Asynchronicity
  • Auditing / Reviewing
  • Binding
  • Buffers
  • Checkers / linters
  • Collections (lists, vectors, hash-tables, etc.)
  • Color
  • Data structure
  • Date / Time
  • Debugging
  • Destructuring
  • Documentation
  • Editing
  • General
  • Highlighting / font-locking
  • Multiprocessing (generators, threads)
  • Networking
  • Packaging
  • Pattern matching
  • Processes (incl. IPC, RPC)
  • Optimization
  • Refactoring
  • Regular expressions
  • Strings
  • Testing
  • User interface
  • Version control
  • XML / HTML

另外提供了大量常用函数的性能分析对比,例如:

#+BEGIN_SRC elisp :exports both :cache yes
  (let ((list (cl-loop for i from 1 to 1000
                       collect i)))
    (bench-multi :times 100
      :ensure-equal t
      :forms (("(-non-nil (--map (when ..." (-non-nil
                                             (--map (when (cl-evenp it) it) list)))
              ("(delq nil (--map (when ..." (delq nil
                                                  (--map (when (cl-evenp it) it) list)))
              ("cl-loop" (cl-loop for i in list
                                  when (cl-evenp i)
                                  collect i))
              ("-select" (-select #'cl-evenp list))
              ("cl-remove-if-not" (cl-remove-if-not #'cl-evenp list))
              ("seq-filter" (seq-filter #'cl-evenp  list)))))
#+END_SRC
#+RESULTS[6b2e97c1ebead84a53fd771684cc3e155e7f6b1e]:
| Form                       | x faster than next | Total runtime | # of GCs | Total GC runtime |
|----------------------------+--------------------+---------------+----------+------------------|
| -select                    |               1.17 |    0.01540391 |        0 |              0.0 |
| cl-loop                    |               1.05 |    0.01808226 |        0 |              0.0 |
| seq-filter                 |               1.13 |    0.01891708 |        0 |              0.0 |
| (delq nil (--map (when ... |               1.15 |    0.02134727 |        0 |              0.0 |
| cl-remove-if-not           |               1.18 |    0.02459478 |        0 |              0.0 |
| (-non-nil (--map (when ... |            slowest |    0.02903999 |        0 |              0.0 |
6 个赞