我最近陆续给 eaf-pdf-viewer 提交了支持按行全文搜索和预览的代码,让 pdf 和 epub 搜索体验接近普通 emacs buffer , 目前支持 ivy ,直接看效果:

在 700 页的 emacs manual PDF 里模糊搜索全文并实时高亮当前行.

在中文 EPUB 里用拼音搜索,并同步页面显示 (
由于 eaf-pdf-viewer 基于的 pymupdf 不支持对 epub 添加标注,所以用 pyqt 绘制箭头指示当前行,和 pdf ui 稍有不同,有些行没有那么精确,但偏差不大)
安装完 eaf-pdf-viewer 后执行 eaf-pdf-narrow-search 命令或者绑定特定按键就可以使用
(eaf-bind-key eaf-pdf-narrow-search "/" eaf-pdf-viewer-keybinding)
对我来说,这体验超过了大部分系统级别的 pdf 或 epub 预览应用了,加上很方便打开 pdf 目录,跳转和用 org mode 编辑目录,用起来很顺畅,
也取代了用了很久的 pdf-tools + nov ,感谢 @manateelazycat , @tumashu 及其他开源贡献者开发
。
使用其他 minibuffer 搜索框架(比如 consult, helm)的道友有兴趣可以尝试把这些前端也加进去, 核心是参考以下函数把 eaf-pdf-narrow–update, eaf-pdf-narrow–quit , eaf-pdf-narrow–done 三个接口串起来:
(defun eaf-pdf-narrow--ivy (cache-file-name eaf-buffer-id &optional current-page)
(let* ((candidates (split-string
(with-temp-buffer
(set-buffer-multibyte t)
(insert-file-contents-literally cache-file-name)
(decode-coding-region (point-min) (point-max) 'utf-8)
(buffer-string)) "\n" t)))
(if (require 'ivy nil t)
(ivy-read
"Narrow Search: "
candidates
:update-fn (lambda ()
(eaf-pdf-narrow--update
eaf-buffer-id
ivy-text
(ivy-state-current ivy-last)
ivy--index ivy--old-cands))
:require-match t
:preselect (format "%s:" current-page)
:action (lambda (selection) (eaf-pdf-narrow--done eaf-buffer-id))
:unwind (lambda () (unless ivy-exit (eaf-pdf-narrow--quit eaf-buffer-id)))
:caller 'eaf-pdf-narrow--ivy)
(message "Please install ivy first."))))
20 个赞
看着体验真不错,已经超越很多 PDF 阅读器了。不知道 eaf-pdf-viewer 在 Mac OS 上现在的使用体验如何,好久没有尝试了。
@MatthewZMD @manateelazycat @twiddling
给大佬们提需求来啦:
由于我这边使用 Mac eaf-pdf,使用鼠标操作很不顺手(鼠标选择文字飘忽不定)。希望对于 Text 类型的 pdf 能有一个类似的 Meow mode 的操作。能够在 eaf 当中快捷的进行文本移动/选择的操作。
这样就可以全键盘的操作了。
虽然可以通过 eaf-pdf-extract-page-text 获取当前页面的 text,但这个需要跳转到另一个 buffer,操作不自然。
我进行了一轮调研和学习,感觉应该可以实现。
pymupdf 可以通过 words = page.get_text(“words”),抽取当前页面的所有单词
格式为:
(x0, y0, x1, y1, "word", block_no, line_no, word_no)
这样就可以快速的通过 word line block 这三种类型,进行快速的导航/选择。这样可以全键盘操作了。
糊了一下通过 word 导航的 demo,测试了一下,还是挺快的。

未来:
- 可以设定一个 mode,
- 未进入mode时,jk 是翻页,
- 进入 mode h/j/k/l 按照单词导航,H/J/K/L 选择多个单词/行等
- 选择后,就可以快速的翻译、记笔记、ai 等等其他的 Emacs 能力
坐等大佬们有时间实现一下。由于我比较菜,要是我来实现,估计遥遥无期,先把需求和想法提一下。
4 个赞
之前的全文搜索功能对论文之类的小的文档很好,但对于大的 pdf 就太慢了,同时考虑到 emacs 本身 minibuffer 框架的过滤搜索就非常好,于是加了一个 cache ,先让 ivy 搜出结果再把具体页码传递过去高亮,这样 eaf 那边只需要在一页里面搜索并显示出来,所以大部分都是基于前人写的搜索的代码,改得代码比我想象的少。
发了一个新 PR,合并之后 epub 里就可以绘制一个箭头来指示当前搜索行了。展示效果的 gif 已经更新到主帖中。
其实pymupdf原本的搜索速度很快,只不过eaf-pdf-viewer包装后变慢了,尝试了以下代码,搜索速度很快,在_search_in_pages中修改
def _search_in_pages(self, text, page_list):
# PdfDocument在fitz.Document基础上增加了功能,导致self.document循环时很慢
for page_index in page_list:
# Search from the current page
page = self.document.document[page_index]
if page_index < self.current_page_index:
self.search_text_index = len(self.search_text_quads_list)
if support_hit_max:
quads_list = self.document.document.search_page_for(page_index, text, hit_max=999, quads=True)
else:
quads_list = self.document.document.search_page_for(page_index, text, quads=True)
if quads_list:
for index, quad in enumerate(quads_list):
search_text_offset = (page_index * self.page_height + quad.ul.y) * self.scale
self.search_text_offset_list.append(search_text_offset)
self.search_text_quads_list.append(quad)
self.search_page_history.add(page)
return quads_list
还得把search_text中page.cleanup_search_text()删除掉,否则弹出错误。
修改之后速度很快,大文件也没问题,不知道这样改有没有问题,大佬可以验证一下
follow link 的功能似乎就是类似 ace-jump 的效果,我以前用过 chrome 插件里 surfingkey 可以按一个键把 chrome 里打开 pdf 视图里的每个单词前标注一两个英文字母(这导致满屏都是附加英文字符),然后按字符把光标移过去,但感觉这类功能更适合要精确编辑的场景,体验过就没再用了,但你说到这功能,确实有人实现,你可以去体验一下试试
这个模式和我在上一楼提到的 chrome 的 surfingkey 插件 带的功能是类似的,它内置了一个 pdf 阅览器,基本就是可以用 vim 按键全键盘操作,比如按一个按键后进入 visual mode, 然后用 vim 按键移动选择,你可以去体验一下,看用起来是否顺畅,js 能实现,python 应该也可以。(我自己用了感觉不是很舒服,所以都不再用这个插件了,还是鼠标直接选更好,我在 linux 上用 eaf-pdf-viewer 鼠标也容易飘,能先解决这个是最好的,但目前用 extract 文本来选择也挺方便,有时候比鼠标选还好,因为可以 emacs 按键多行去选,而且可以在 extract buffer 里基于原文临时编辑成自己的话,然后复制到笔记里)
是把 self.document 改成 self.document.document 吗?用原始的 fitz.Document?
如果你改了效率更高,也可以直接优化它给 eaf 发 PR,因为这个 ivy 搜索和原本的 C-s 搜索都基于这个函数,二者都获益,而且功能上也算是互补的,C-s 在按关键字精确搜索的场景下很好用,ivy 搜索主要是利用minibuffer 的那些模糊搜索机制来查,还可以导出或者复制搜索候选项等。
但我感觉原来的慢是因为每次重新去搜整个文档,所以即便单页很快,遇上百甚至上千页的文档也很慢,可以改成每次只搜当前页面,超过页面再去搜下一页是更合理的,后面再具体看一看。
刚试了下1700页的pdf文件,确实会慢点,等待大概1到2秒吧,可以接受吧
我在一台 2020 年左右的 AMD Ryzen 7 4800H 的 cpu 机器上,ubuntu 系统, elisp 文档(1300 多页)搜 debugging 这个词,结果有 100 多个选项,用现在的 self.document 搜得卡 6,7 十秒,改成 self.document.document 搜可能 40s 左右,是更快,但还是用不起来(20 页以下pdf基本是即时返回结果)。
可能是以下 PdfDocument getitem 使得从 document 里索引 page 更慢了
class PdfDocument(fitz.Document):
...
def __getitem__(self, index):
if index in self._page_cache_dict:
page = self._page_cache_dict[index]
if not self._is_trim_margin:
return page
if page.cropbox == self._document_page_clip:
return page
page = PdfPage(self.document[index], index, self.document.is_pdf)
...
确实,可能跟pdf内容有关系,我上面用的那个1700页的pdf都是英文的,就卡顿一两秒。换一个中文质量不高的文档就卡顿很久。
还有现在的搜索机制是随输入内容不断更新页面,例如搜“search”,输入s就开始全文搜索,输入se又全文搜索,等全输入了就快卡死了。我写了个包装,使得完成全部输入之后才发送到eaf-pdf-viewer侧,感觉可以接受
(defun eaf-pdf-search-with-text (text)
(eaf-call-sync "execute_function_with_args" eaf--buffer-id "send_input_message" "Search Text: " "search_text" "search" text))
(defun eaf-pdf-search-text-wrap (fn)
(let ((text (unless eaf-search-input-active-p
(read-from-minibuffer "Search Text: " nil (let ((input-text-map (copy-keymap minibuffer-local-map)))
(define-key input-text-map "\C-s" #'exit-minibuffer)
(define-key input-text-map "\C-r" #'exit-minibuffer)
input-text-map)))))
(if (and text (not (string-empty-p text)))
(eaf-pdf-search-with-text text)
(eaf-call-async "eval_function" eaf--buffer-id fn (key-description (this-command-keys-vector))))))
(defun eaf-pdf-search-text-forward ()
(interactive)
(eaf-pdf-search-text-wrap "search_text_forward"))
(defun eaf-pdf-search-text-backward ()
(interactive)
(eaf-pdf-search-text-wrap "search_text_backward"))
1 个赞
这种情况确实快不少,我这在 elisp 文档里搜 debugging 能 10s 内了,在小几百页的场景下基本能实时用了,
1 个赞
欢迎大佬继续补丁呀,我虽然不用ivy,但是看着搜索好丝滑,点赞
1 个赞