Snails超级快的模糊搜索框架
Emacs
字数3226 2019-07-21

Anything, Helm, Ivy

十几年前和rubikitchThierry Volpiatto一起创建了Anything这个模糊搜索框架。

Anything做为当时第一个模糊搜索框架,因为其多后端搜索和强大的框架设计,比当时 ido 的效率要快很多,特别是当你要模糊搜索多个后端,却不想记一堆快捷键的时候,Anything真的很爽。

后来我和rubikitch都忙于工作,Thierry Volpiatto十几年坚持不懈的做了很多代码改进和重构,以至于后来创建了更为健壮和模块化的Helm, Helm到目前为止都是Emacs下最强大的模糊搜索框架。

最近几年,超级黑客Oleh Krehel创建的Ivy, 以其简洁的交互和超快的速度,俘获了大批Emacser的青睐。

Snails一个超快的、现代化和易于扩展的模糊搜索框架

我曾经也尝试过Ivy, 非常简洁,而且速度超快,一点都不卡,但是Ivy有一个和我习惯冲突的设计是,Ivy一次只能搜索一个后端,对于我这种常年用Anything/Helm的老年人来说,让我按照分类来记住不同的搜索后端快捷键,真的很麻烦。

同时,Helm发展了十几年的时间,虽然非常成熟和强大,但是想要基于Helm写一个新的插件,都要看半天文档和小心各种细节。

所以,我有时候在想,我能否重新设计一个新的模糊搜索框架,兼具Helm和Ivy的优点呢?

  1. 像Helm那样支持多个后端同时搜索,特别是最近文件 + 已经打开的文件 + 本地磁盘全局搜索三个后端配合一起搜索的时候,几乎不存在找不到的文件
  2. 像Ivy那样快,不管怎么搜索都是秒开,而且搜索的过程中不要让Emacs变得卡顿
  3. 界面像 Sublime/VSCode 那样,弹出一个单独的固定窗口,不要像Helm那样破坏窗口布局,也不要像Ivy那样在minibuffer上随着输入来回跳动
  4. 交互易用,就是输入搜索字符,上下移动后,按回车就行了,没有其他复杂操作
  5. 插件门槛的编写达到最低,只要会基本的Elisp语法,会写过滤函数,任何插件都应该在5分钟之内完成

所以我创建了Snails这个全新的模糊搜索插件来满足我上面的全部要求, 发一张效果图。

snails

安装

因为这个插件还在活跃开发中,安装和使用文档请见 Github

插件介绍

编写Snails的插件真的超级简单,只要你会基本的Elisp语法,会编写列表过滤函数,你可以在5分钟之内编写完一个新的插件!

Snails从分类上有两种插件语法:同步插件和异步插件

编写同步插件

我们用内置的snails-backend-recentf插件来讲解怎么编写同步插件:

(require 'snails-core)
(require 'recentf)

(recentf-mode 1)

(snails-create-sync-backend
 :name
 "RECENTF"

 :candidate-filter
 (lambda (input)
   (let (candidates)
     (dolist (file recentf-list)
       (when (or
              (string-equal input "")
              (string-match-p (regexp-quote input) file))
         (add-to-list 'candidates
                      (list
                       (snails-wrap-file-icon file)
                       file)
                      t)))
     candidates))

 :candiate-do
 (lambda (candidate)
   (find-file candidate)))

(provide 'snails-backend-recentf)

以上面这段插件代码,它的意思是,如果用户什么都没有输入,显示所有最近浏览的文件,如果有输入,根据用户输入来过滤最近浏览的文件,用户确认后,用 find-file 这个命令打开文件。

编写异步插件

我们用内置的snails-backend-mdfind插件来讲解怎么编写异步插件:

(require 'snails-core)

(snails-create-async-backend
 :name
 "MDFIND"

 :build-command
 (lambda (input)
   (when (and (featurep 'cocoa)
              (> (length input) 5))
     (list "mdfind" (format "'%s'" input))))

 :candidate-filter
 (lambda (candidate-list)
   (let (candidates)
     (dolist (candidate candidate-list)
       (add-to-list 'candidates
                    (list
                     (snails-wrap-file-icon candidate)
                     candidate)
                    t))
     candidates))

 :candiate-do
 (lambda (candidate)
   (find-file candidate)))

(provide 'snails-backend-mdfind)

以上面这段插件代码举例,如果用户输入 awesome, 他会构建一个命令字符串列表 (list "mdfind" "'awesome'") 用于创建搜索子进程,当子进程完成时,进程返回候选列表给candidate-fitler过滤函数, 过滤函数过滤Shell命令返回的结果进行候选词渲染。用户确认后,用 find-file 这个命令打开文件。

Snails非常智能,它会管理所有异步后端的子进程,当用户修改输入时,Snails会自动创建新的子进程继续搜索,同时把那些旧的子进程杀掉。不管用户输入多快,都不会卡主Emacs。

最后

Snails插件编写简单吧?

如果你和我有同样的口味,欢迎试用。

如果你想玩一下Elisp,欢迎编写新的插件。;)