[解决]为懒猫的 blink-search 添加搜索 PDF 等其他文档的后端

问题

我的 Emacs 中既安装了 pdf-tools,也有安装 eaf-pdf-browser。

并且设置 blink-search-grep-pdf-backend:

    (setq blink-search-grep-pdf-backend 'pdf-tools)

默认使用 pdf-tools 预览 blink-search 搜索的结果。

但是在实际使用中出现错误,如下:

Debugger entered--Lisp error: (error "Buffer is not associated with any file :PDF")
  error("Buffer is not associated with any file :%s" "PDF")
  pdf-info--normalize-file-or-buffer(nil)
  pdf-info-number-of-pages()
  pdf-cache-number-of-pages()
  pdf-view-goto-page(1)
  blink-search-grep-pdf-pdftool-goto("/Users/c/Library/Mobile Documents/iCloud~QReader~M..." 1 "D1080")
  (cond ((and (eq blink-search-grep-pdf-backend 'pdf-tools) (featurep 'pdf-tools)) (blink-search-grep-pdf-pdftool-goto file page submatches)) ((and (eq blink-search-grep-pdf-backend 'eaf-pdf-viewer) (featurep 'eaf-pdf-viewer)) (eaf-pdf-jump-to-page file page)) (t (message "Unknown backend %s" blink-search-grep-pdf-backend)))
  blink-search-grep-pdf-do("/Users/c/Library/Mobile Documents/iCloud~QReader~M..." 1 "D1080")
  (let ((match-file (blink-search-grep-file-get-match-buffer file))) (blink-search-grep-pdf-do file page submatches) (if match-file nil (add-to-list 'blink-search-grep-file-temp-buffers (current-buffer))))
  (progn (select-window input-window) (blink-search-preview-select-window) (let ((match-file (blink-search-grep-file-get-match-buffer file))) (blink-search-grep-pdf-do file page submatches) (if match-file nil (add-to-list 'blink-search-grep-file-temp-buffers (current-buffer)))) (select-window input-window))
  (if input-window (progn (select-window input-window) (blink-search-preview-select-window) (let ((match-file (blink-search-grep-file-get-match-buffer file))) (blink-search-grep-pdf-do file page submatches) (if match-file nil (add-to-list 'blink-search-grep-file-temp-buffers (current-buffer)))) (select-window input-window)))
  (when input-window (select-window input-window) (blink-search-preview-select-window) (let ((match-file (blink-search-grep-file-get-match-buffer file))) (blink-search-grep-pdf-do file page submatches) (if match-file nil (add-to-list 'blink-search-grep-file-temp-buffers (current-buffer)))) (select-window input-window))
  (let* ((inhibit-message t) (input-window (get-buffer-window blink-search-input-buffer))) (when input-window (select-window input-window) (blink-search-preview-select-window) (let ((match-file (blink-search-grep-file-get-match-buffer file))) (blink-search-grep-pdf-do file page submatches) (if match-file nil (add-to-list 'blink-search-grep-file-temp-buffers (current-buffer)))) (select-window input-window)))
  (blink-search-select-input-window (let ((match-file (blink-search-grep-file-get-match-buffer file))) (blink-search-grep-pdf-do file page submatches) (if match-file nil (add-to-list 'blink-search-grep-file-temp-buffers (current-buffer)))))
  blink-search-grep-pdf-real-preview("/Users/c/Library/Mobile Documents/iCloud~QReader~M..." 1 "D1080")
  (closure ((submatches . "D1080") (page . 1) (file . "/Users/c/Library/Mobile Documents/iCloud~QReader~M...") t) nil (blink-search-grep-pdf-real-preview file page submatches))()
  apply((closure ((submatches . "D1080") (page . 1) (file . "/Users/c/Library/Mobile Documents/iCloud~QReader~M...") t) nil (blink-search-grep-pdf-real-preview file page submatches)) nil)
  timer-event-handler([t 0 0 500000 nil (closure ((submatches . "D1080") (page . 1) (file . "/Users/c/Library/Mobile Documents/iCloud~QReader~M...") t) nil (blink-search-grep-pdf-real-preview file page submatches)) nil idle 0 nil])

这个错误栈是 blink-search-grep-pdf-pdftool-gotopdf-view-goto-page(1) 函数抛出的。

(defun blink-search-grep-pdf-pdftool-goto (file page submatches)
  (find-file file)
  (pdf-view-goto-page page)
  (let ((matches (pdf-isearch-search-page submatches)))
    (pdf-isearch-hl-matches (nth 0 matches) matches t)))

因为实际上我要预览的 PDF 是被 eaf-pdf-browser 打开,并不是 pdf-tools 打开,我猜测是 eaf-pdf-browser 拦截了 blink-search-grep-pdf-pdftool-gotofind-file?我看到 eaf 源代码中:

(defun eaf--find-file-advisor (orig-fn file &rest args)
  "Advisor of `find-file' that opens EAF supported file using EAF.

It currently identifies PDF, videos, images, and mindmap file extensions."
  (let ((fn (if (commandp 'eaf-open)
                #'(lambda (file)
                    (eaf-open file))
              orig-fn))
        (ext (file-name-extension file)))
    (if (eaf--find-file-ext-p ext)
        (apply fn file nil)
      (apply orig-fn file args))))
(advice-add #'find-file :around #'eaf--find-file-advisor)

我将 eaf-pdf-extension-list 识别的拓展名列表将 PDF 格式删除 (setq eaf-pdf-extension-list '("xps" "oxps" "cbz" "epub" "fb2" "fbz"))

eaf-pdf-extension-list is a variable defined in `eaf-pdf-viewer.el'.
Its value is ("xps" "oxps" "cbz" "epub" "fb2" "fbz")

Original value was 

("pdf" "xps" "oxps" "cbz" "epub" "fb2" "fbz")

这样 blink-search 就能利用 pdf-tools 预览,不会被 eaf-pdf-browser 拦截。

需求

但是我除了预览,其他情形下希望使用 eaf-pdf-browser 查看 PDF,但是因为将 PDF 拓展名删除了,eaf-open 命令无法打开 PDF 格式文件了:

if: [EAF] File /Users/c/Downloads/SSM/737-678_YUN_SSM_D280A242_TD/PDF/00___006.PDF cannot be opened.

方案

该如何解决这种冲突?在调用 blink-search-grep-pdf-do

(defun blink-search-grep-pdf-do (file page submatches)
  ;;highlight the matches
  (cond
   ((and (eq blink-search-grep-pdf-backend 'pdf-tools) (featurep 'pdf-tools))
    (blink-search-grep-pdf-pdftool-goto file page submatches))
   ((and (eq blink-search-grep-pdf-backend 'eaf-pdf-viewer) (featurep 'eaf-pdf-viewer))
    (eaf-pdf-jump-to-page file page))
   ;; TODO support other pdf backends
   (t (message "Unknown backend %s" blink-search-grep-pdf-backend))))

前临时将 find-file 关于 eaf-pdf-browser 的 advice 删除,执行完后重新加回来? 还是有其他更优雅的方案?

在你的自定义函数中, 临时在 let 里面覆盖 eaf-pdf-extension-list 的值就好了, 离开你的自定义函数作用域后, eaf-pdf-extension-list 就会恢复成你配置的值。

    (defun hurricane//blink-search-grep-pdf-do (file page submatches)
      ;;highlight the matches
      (cond
       ((and (eq blink-search-grep-pdf-backend 'pdf-tools) (featurep 'pdf-tools))
        (let ((eaf-pdf-extension-list '("xps" "oxps" "cbz" "epub" "fb2" "fbz")))
         (blink-search-grep-pdf-pdftool-goto file page submatches)))
       ((and (eq blink-search-grep-pdf-backend 'eaf-pdf-viewer) (featurep 'eaf-pdf-viewer))
        (eaf-pdf-jump-to-page file page))
       ;; TODO support other pdf backends
       (t (message "Unknown backend %s" blink-search-grep-pdf-backend))))

    (advice-add #'blink-search-grep-pdf-do :override #'hurricane//blink-search-grep-pdf-do)

用 advice-add override 解决了,感谢大佬帮忙。

发现一个情况, 搜索的到的结果为

text:$D1/民用飞机运营事件风险评估方法研究.pdf:44: 假设事件发生概率与产品寿命无关,观测到的事件服从泊松分布,取置信度为 1-α,则

match[0],match[1] 分别为,133,145。

而 len(text) 为 69,很明显:text[match[0]:match[1]] 是切片不到结果的。这样导致 blink-search-grep-pdf-pdftool-goto file page submatches) 中的 submatches 参数得到的是 "" 空字符串,进而 pdf-tools 抛出错误,因为 pdf-tools 提示高亮搜索是不允许空字符串。

我没仔细理解你代码的含义,只是简单的改为

candidate['match_text'] = text[match[0]:match[1]] if text[match[0]:match[1]] else prefix

可以正常高亮 blink-search input 中的搜索关键字,不知道对不对,能否看看?

这个好像是rga没有返回正确的match导致的。用prefix作为match_text应该也行,只不过当prefix中有正则的情况可能就无法highlight了