elpl: 一个简陋但是不污染当前 Emacs 的 elisp repl

由于内置的 ielm 会污染当前 Emacs,所以想要一个比较“卫生”的 repl。简单用用还可以:

53%20PM

感谢 @xuchunyang batch 模式下如何 read 多行字符? - #8,来自 xuchunyang 帮我解决关键问题。

4 个赞

感觉有些用处,有时需要从 Emacs -Q 试下代码。

反馈一个问题:我这边只按换行会卡死,一直输出 "ELPL> " 好像就没事:

(if (string= s "")
    "ELPL> "
  "")

刚刚修复了。

当 s 内容只有 “\n” 的时候 (eval (read s)) 就会卡死。

感觉不是这个原因,(eval (read "\n")) 不会卡死:

(eval (read "\n"))
;; error-> End of file during parsing

下面这里按换行也会卡死:

ELPL> (+ 1

我把测试用例补上了,包括单行/多行/未闭合的字符串和 sexp。

发现问题出在 candition-case 第二个参数,这里本应该是一个 HANDLER:

(condition-case err
    (progn
      (unless (string= s "\n")
        (print (eval (read s))))
      (setq s ""))
  (end-of-file) ;; <<-----------
  (error
   (setq s "")
   (print err)))

正是这第二个参数,使得多行表达式能成功执行。也使得未闭合表达式卡死。


(end-of-file) 其实是 eval 输出的错误信息,如果把这句屏蔽:

(condition-case err
    (progn
      (unless (string= s "\n")
        (print (eval (read s))))
      (setq s ""))
  ;; (end-of-file) ;; <<-----------
  (error
   (setq s "")
   (print err)))

问题似乎回到了原点:

  1. 未闭合的字符串/表达式

    ELPL> "foo<RET>
    
    (end-of-file)
    ELPL>
    
  2. 续行

    ELPL> "foo<C-j>
    bar"<RET>
    
    (end-of-file)
    ELPL> 
    (void-variable bar)
    ELPL> 
    

第 1 种比较好理解,没闭合就是 end-of-file 了。

第 2 种情况比较复杂,因为两个错误是分别产生、但几乎同时打印出来。在 (end-of-file) 的时候无法预料后面是否 (void-variable bar)

目前只能顾第 2 种错误,放任第 1 种错误了。


去读一读源代码,看看是否能找到完美解决方案。


或许可以在 comint-send-input 判断字符串/表达式是否闭合,以阻止发送不完整的表达式。参考 eilm-return

增加了一个 elpl-return 函数并绑定到回车键。当表达式未闭合的时候,按回车不是提交,而改为插入新行。避免了求值出错造成卡死的问题。

这样好像就可以用了(不会卡死)?

(apply #'make-comint "ELPL" elpl-cli-file-path nil elpl-cli-arguments)

我改用 elpl-return 提交数据可以避免卡死,ielm 也是这样做的,感觉这种方案更好。

剩下其他错误在 condition-case 里面处理。

看起来挺有用,会上melpa吗?

上面提到的问题差不多已经解决了。

等我把测试补完,没测出新问题就上 melpa。

嗯,elpl-return 应该也行,但我这边下面这样不会卡:

(apply #'make-comint "ELPL" elpl-cli-file-path nil elpl-cli-arguments)

(换行是我👋打的)

ELPL> (+
1
2
3
4
5



)

15
ELPL> 
ELPL> 
ELPL> 
ELPL>

也是一个思路,稍后我也验证一下。

输出开头不知道为什么多个换行?

刚刚 merge 到 melpa 了,过几个小时应该可以下载:MELPA

1 个赞

增加了 edit-direct 支持,大大降低误操作率。

在交互式状态下,按回车键有两种响应:

  1. 表达式闭合 -> 提交
  2. 表达式未闭合 -> 换行

看起来挺简单,实际中却常常因为这个小小的差别而受挫:为了少打括号,我开启了括号、引号补全。但这样一来表达式始终都是闭合的,想换行就必须用 C-j 代替,稍不留神就直接按了 RET,把未输入完的表达式提交执行了。

关闭括号、引号的补全显然也是不可取的。所以,既然想不到什么好的解决方案,不如跳出 comint-mode 的限制,一可以真正杜绝误提交, 二可以享受在一个正常的 emacs-lisp-mode 中编辑代码的畅快。

可以仿照lisp-interaction-mode里的eval-print-last-sexpC-j)不过改成用单独的Emacs执行

增加了对 completion-at-point 的支持。

之前一直没想明白应该怎么写,而且只要设置了 completion-at-point-functions 就能补全当前 Emacs 的符号,算是有些帮助。但实际上这是不对的,应该补全 elpl 进程内部的符号。

现在简单实现了 elpl 进程内部符号的补全,效果如下:

  • 无配置 (按 C-M-i)

Screenshot_2020-08-21_at_2.37.09_PM__elpl-completion-at-point_28.0

  • 有配置 (company 自动接管,无需额外设置)

Screenshot_2020-08-21_at_2.50.27_PM__elpl-completion-at-point_28.0_conf