使用Emacs作为全局启动器

前几天发布了帖子:使用 Emacs 作为万能粘合剂 介绍了,最近编写的几个 emacs 工具。其中,Global Interactive Emacs 适合单独拿出来讨论一下。

这篇文章作为基础,实现的类似 lauchbar 或者 Alfred 应用启动工具,能够快速的进行应用启动、链接跳转、常用工作流的快捷操作。

核心逻辑是: hack completing-read-function 方法,让外部工具(choose/rofi)去处理 completing-read 的输入输出。

当前我已经用它替代了日常使用 lauchbar 的需求。 目前实现的功能有:

  • global-interactive-select-from-clipboard: 从 kill-ring and clipboard 选择文本。
  • global-interactive-run-app: 启动 MaOS 安装的 app 。
  • global-interactive-open-url: 使用浏览器打开预定义的网址。
  • global-interactive-leetcode: 交互性查询并显示 leetcode 问题。
  • global-interactive-kubectl: 交互式查询并打开 K8S 资源配置。
  • global-interactive-youdao-dictionary: 由有道字典翻译单词。
  • global-interactive-web-search: 使用浏览器通过预定义的搜索引擎查询信息。
  • global-interactive-send-request: 使用 chrome cookie 发送 http 请求,并解析它的 json 响应。
  • global-interactive-find-file: 在系统中查找文件并对此文件执行一些操作。
  • global-interactive-chrome-bookmarks: 查询 Chrome 书签。
  • global-interactive-chrome-history: 查询 Chrome 历史。

这个工具的主要优点:

  • 免费: 不管是 Lauchbar 还是 Alfred,你都需要付费才能舒适有效地使用它。这个工具只需要 Emacs 和一些可以免费使用的开源软件。
  • 一致性: 您可以在 Emacs 内部或外部使用相同的特性,并有类似的体验。
  • 简单配置: 如果您熟悉 Emacs,您可以轻松地使用 Emacs-lisp 配置它,而不必重新学习其他工具的语言和工作流。
  • 可扩展: 可以编写自己的交互式函数并将它们嵌入到工具中,或者使用其他人开发的 Emacs 插件,提取交互式函数并将它们插入到工具中。 示例:

global-interactive-select-from-clipboard

global-interactive-run-app

global-interactive-open-url

global-interactive-kubectl

global-interactive-leetcode

global-interactive-youdao-dictionary

global-interactive-web-search

global-interactive-send-request

global-interactive-find-file

global-interactive-chrome-bookmarks

lobal-interactive-chrome-history

不足与改进

这个工具没有 lauchbar 或者 Alfred 那么强大。目前它只对我自己的需要有用。我会根据自己的需要不断改进,欢迎任何人来帮助我改进。如果大家有什么使用lauchbar/Alfred 过程中积累的比较便捷的工具流,欢迎提出建议。

目前使用外部工具不能完全支持所有Emacs交互函数,例如execute-extended-command 它运行 completing-read 的入参是一个函数而不是一个候选列表,目前我还没有办法让外部工具来处理这种情况。

我尝试进行另一种尝试:并不是使用外部工具来进行交互,而是创建一个Emacs frame,它只包含交互的部分,而不是整个Emacs窗口。

posframe 是一个很好的参考。无奈 posframe 当前创建的是当前 frame 的一个子frame。因此我想到了一个比较 tricky 的方法:

  1. 创建一个透明的、全屏的 frame
  2. 然后在这个 frame 下,调用posframe (直接使用vertico-posframe)
  3. 适当的时机(我还没找到这个适当是什么时候)删除创建的透明frame

问题:

  1. 是否这样的思路合适,感觉这样,整个流程只是比在Emacs内部使用,少了一个切换到Emacs的过程。
  2. 不知道在哪个时刻删除预先创建的 frame 比较合适,因为运行的过程中,可能会有异步的运行,需要等待一段时间弹出候选窗口。(目前我是没有自动的删除 frame 的逻辑,是运行 C-g 时,删除所有创建的透明 frame) global-interactive-interactive

其他问题: elisp 程序该怎么编写单元测试或者集成测试?

欢迎大家使用,积极的提供意见,无比感谢。

6 个赞

https://www.gnu.org/software/emacs/manual/html_mono/ert.html

1 个赞

:joy: :joy: 原来官方文档里就有呀。我看看。感谢。

为什么一定要用posframe创建child frame呢?直接创建一个frame不行吗?

因为简单。

最好是直接创建一个frame。只是这样相当与要重新实现一个类似 vertico-posframe 的功能。如果直接使用 vertico-posframe,那么只需要处理什么时候创建主 frame 和怎么删掉它就行了。

不过,直接创建 frame 应该是最终的解决方法。

@tumashu 能不能提个需求,让 vertico-posframe 作为一个单独 frame 运行。 :ghost: :ghost: :ghost:

目的是干什么?

global-interactive-interactive 不用切换到 Emacs 主 frame 就可以运行 Emacs 的交互函数。

如果vertico-posframe 作为一个单独 frame 运行,那就可以把弹出vertico-posframe 绑定到某个快捷键上,如图,在不用切换到 Emacs 的情况下,快速的运行一些既定的 Emacs 函数。

局限性太大了,这样还必须依赖于vertico、posframe和vertico-posframe。就是最底层的make-frame不行吗?

尝试使用 GitHub - SebastienWae/app-launcher: Launch application from Emacs

(defun eat/emacs-app-launcher ()
    ""
    (interactive)
    (let ((default-frame-alist '((undecorated . t)
                                 (vertical-scroll-bars)
                                 (scroll-bar-mode . 0)
                                 (menu-bar-lines . 0)
                                 (tool-bar-lines . 0))))
      (with-selected-frame
          (make-frame
           '((name . "emacs-app-launcher")
             (minibuffer . only)
             (width . 120)
             (height . 11)))
        (unwind-protect
            (app-launcher-run-app)
          (delete-frame)))))

但是运行 emacsclient -e "(eat/emacs-app-launcher)" 会 Error:

*ERROR*: Unknown terminal type

不太会 elisp, 暂时不知道如何进行 :cold_face:

行是行,只是直接 make-frame 的话,许多 vertico、posframe 踩过的坑,估计还得踩。要好用的话,估计还得慢慢开发。如果有现有的工具的话,还是希望可以复用的。

嗯嗯,我看看。看起来核心就是make-frame 创建一个只有 minibuffer 的 frame。

感觉不太靠谱,不过如果下面的函数返回的是 (x . y), 创建的就是普通 frame,这个功能是为 exwm 设计的,因为窗口遮挡问题。

(defcustom vertico-posframe-refposhandler #'vertico-posframe-refposhandler-default
  "The refposhandler used by vertico-posframe.

NOTE: This variable is very useful to EXWM users."
  :type 'function)


(defun vertico-posframe-refposhandler-default (&optional frame)
  "The default posframe refposhandler used by vertico-posframe.
Optional argument FRAME ."
  (cond
   ;; EXWM environment
   ((bound-and-true-p exwm--connection)
    (or (ignore-errors
          (let ((info (elt exwm-workspace--workareas
                           exwm-workspace-current-index)))
            (cons (elt info 0)
                  (elt info 1))))
        ;; Need user install xwininfo.
        (ignore-errors
          (posframe-refposhandler-xwininfo frame))
        ;; Fallback, this value will incorrect sometime, for example: user
        ;; have panel.
        (cons 0 0)))
   (t nil)))

我试了一下,因为焦点问题,可能未必可行。

好的,我先看看。感谢。

你动图里的 ui 是用的什么工具?

你指的UI是?emacs 外部使用的是 choose ,内部是 vertico-posframe

1 个赞

我用的浏览器不是Chrome,而是Brave,安装 GIE 的时候出现如下出错提示:

目前访问浏览器 History 只支持 Chrome,把 (require 'global-interactive-chrome-bookmarks) (require 'global-interactive-chrome-history) 这两个引入去掉吧。

按照提示,尝试用 vertico-posframe 创建一个独立的 frame 用于交互。但并没有成功。vertico-posframe 的焦点处理让它还是集中与原来 Emacs frame。 现在 Global Interactive Emacs 提供了三种交互形式:

  1. 直接在Emacs内部运行。
  2. 创建一个透明的 Frame,在这个 Frame 中运行 vertico-posframe
  3. 使用 Choose 进行交互。

经过最近一段事件的使用。使用 Choose 进行交互已经可以满足大部分的需求。但是有一些复杂的Emacs命令还无法支持。

而使用一个透明的 Frame,在这个 Frame 中运行 vertico-posframe,可以支持更多的 Emacs 命令。目前遇到的唯一缺陷时,当退出 Frame 时,没法自动聚焦到上一次访问的app。(Choose 目前最大的优势就在于,使用完之后,聚焦到上一次的应用当中。)