[提问]让pdf-tools 在isearch-repeat-forward/backward 时显示高亮?

pdf-tools在使用 isearch-forward 根isearch-backward是是可以显示高亮的,然而一旦使用isearch-repeat-forward/backward高亮就没了。evil用户习惯于用n/N来向前/向后搜索,没了这个还是挺不方便的。。

以下是实现方法,有点hack,不过体验良好。 由于实现方法有点hack,难以保证未来(及以前)版本的pdf-tools可以使用, 如果以后不能用的话,就无视此贴吧,大不了用 C-sC-s M-s o等, 不过pdf-tools看起来不会大改的样子。

功能:

  • 按n/N搜索时保持高亮
  • 按/键搜索,如果搜索成功,则按回车后不要清除高亮,如果搜索失败,则照常清除高亮。
  • 按ESC清除搜索高亮

分有使用evil-collection包和没有使用的情况, 使用evil-collection包还得避免按键绑定被evil-collection覆盖。

先是没有使用evil-collection包的版本:

(with-eval-after-load 'pdf-tools
  (defun my/isearch-failed? ()
    (or (not isearch-success) isearch-error))

  (defvar-local my/pdf-isearch-highlight-matches nil)
  (defun my/pdf-isearch-cleanup-highlight ()
    (setq my/pdf-isearch-highlight-matches nil)
    (pdf-view-redisplay))

  (defun my/pdf-isearch-hl-matches-controllable-highlight (orig-fun current matches &optional occur-hack-p)
    (funcall orig-fun current matches (or my/pdf-isearch-highlight-matches occur-hack-p)))
  (advice-add #'pdf-isearch-hl-matches
              :around #'my/pdf-isearch-hl-matches-controllable-highlight)

  (defun my/pdf-isearch-repeat-forward (&optional arg)
    (interactive "P")
    (setq my/pdf-isearch-highlight-matches t)
    (isearch-repeat-forward arg))

  (defun my/pdf-isearch-repeat-backward (&optional arg)
    (interactive "P")
    (setq my/pdf-isearch-highlight-matches t)
    (isearch-repeat-backward arg))

  ;; 搜索超过文档结尾时,直接wrap到文档头开始搜索,不要暂停,
  ;; 如果不这样设置,则搜索超过文档结尾时,暂停的时候,结尾的高亮会不见
  ;; 估计不难修复,但是这样搞更简单。
  (add-hook 'pdf-view-mode-hook
            (lambda () (setq-local isearch-wrap-pause 'no)))

  (defun my/pdf-view-force-normal-state ()
    (interactive)
    (evil-force-normal-state)
    (my/pdf-isearch-cleanup-highlight))

  (advice-add #'pdf-isearch-mode-initialize
              :before
              (lambda (&rest args)
                "在正常使用C-s等进行搜索的时候,重置 `my/pdf-isearch-highlight-matches'。"
                (setq my/pdf-isearch-highlight-matches nil)))

  (defun my/pdf-isearch-mode-cleanup ()
    "按/键搜索,如果搜索成功,则按回车后不要清除高亮,如果搜索失败,则清除高亮。"
    (pdf-isearch-active-mode -1)
    (when (my/isearch-failed?) (pdf-view-redisplay)))
  (advice-add #'pdf-isearch-mode-cleanup
              :override #'my/pdf-isearch-mode-cleanup)

  (evil-define-key 'normal pdf-view-mode-map
    ;; 按n/N 向前/向后搜索,按ESC则返回normal state的同时清除搜索高亮
    (kbd "n") #'my/pdf-isearch-repeat-forward
    (kbd "N") #'my/pdf-isearch-repeat-backward
    (kbd "<escape>") #'my/pdf-view-force-normal-state)
  ;; 或者,如果你有用general.el包的话,可以改用`general-def'定义按键绑定。
  ;; (general-def 'normal 'pdf-view-mode-map
  ;;   "n" #'my/pdf-isearch-repeat-forward
  ;;   "N" #'my/pdf-isearch-repeat-backward
  ;;   "<escape>" #'my/pdf-view-force-normal-state)
  )

接下来是使用evil-collection包的版本,要避免定义的按键绑定被evil-collection覆盖,改一个地方:

(with-eval-after-load 'pdf-tools
  ;; ...此处省略,和前面一样

  ;; 把下面这段

  ;; (evil-define-key 'normal pdf-view-mode-map
  ;;   ;; 按n/N 向前/向后搜索,按ESC则返回normal state的同时清除搜索高亮
  ;;   (kbd "n") #'my/pdf-isearch-repeat-forward
  ;;   (kbd "N") #'my/pdf-isearch-repeat-backward
  ;;   (kbd "<escape>") #'my/pdf-view-force-normal-state)

  ;; 改成下面这段
  (defmacro my/modify-evil-collection-key-bindings (mode &rest body)
    (declare (indent defun))
    (let ((feature-name-var (gensym "feature-name"))
          (mode-var (gensym "mode"))
          (mode-str-var (gensym "mode-str"))
          (hook-fun-name-var (gensym "hook-fun-name-var")))
      `(let* ((,mode-var ,mode)
              (,mode-str-var (symbol-name ,mode-var))
              (,feature-name-var (intern (concat "evil-collection-" ,mode-str-var))))
         (if (featurep ,feature-name-var)
             ;; 在当前evil-collection的实现中,如果feature名对应的文件
             ;; 已经加载,则对应的按键setup函数也已经调用,故在这种情况下,
             ;; 我们可以直接执行body。
             (progn ,@body)
           ;; 否则,添加hook到`evil-collection-setup-hook',
           ;; 判断mode名匹配后执行body。
           (let ((,hook-fun-name-var
                  ;; 这里使用gensym生成hook函数名,由于生成的符号是未intern的,
                  ;; 这会导致evil-collection无法调用我们的hook函数,于是我们需要
                  ;; 提前intern下,同时由于gensym会在符号名后面附加一个全局计数器,
                  ;; 加上我们的函数有前缀 my/ ,因此一般不会产生名称冲突。
                  (intern
                   (symbol-name
                    (gensym (concat "my/modify-evil-collection-key-bindings-"
                                    ,mode-str-var))))))
             ;; 使用defun底层使用的defalias,不然defun不会eval函数名参数。
             (defalias ,hook-fun-name-var
               (lambda (mode keymaps)
                 (when (eq mode ,mode-var)
                   ,@body))
               (concat "修改evil-collection设置的" ,mode-str-var "的按键绑定。"))
             (add-hook 'evil-collection-setup-hook ,hook-fun-name-var))))))

  (my/modify-evil-collection-key-bindings 'pdf
    (evil-define-key 'normal pdf-view-mode-map
      ;; 按n/N 向前/向后搜索,按ESC则返回normal state的同时清除搜索高亮
      (kbd "n") #'my/pdf-isearch-repeat-forward
      (kbd "N") #'my/pdf-isearch-repeat-backward
      (kbd "<escape>") #'my/pdf-view-force-normal-state))
  )
2 个赞

完美解决 :+1:,pdf-tools现在基本不更新了,应该能稳定用很长时间。

之前忘记实现记住搜索方向了,每次搜索不管是按 / 还是 ? 开始搜索,之后按n都是向后搜索,按N都是向前搜索,正常如果按 ? 开始搜索则应该反过来,补上。

不用evil-collection包的版本:

(with-eval-after-load 'pdf-tools
  (defun my/isearch-failed? ()
    (or (not isearch-success) isearch-error))

  (defvar-local my/pdf-isearch-highlight-matches nil)
  (defun my/pdf-isearch-cleanup-highlight ()
    (setq my/pdf-isearch-highlight-matches nil)
    (pdf-view-redisplay))

  ;; 模仿occur-hack-p,注入变量以控制高亮
  (defun my/pdf-isearch-hl-matches-controllable-highlight (orig-fun current matches &optional occur-hack-p)
    (funcall orig-fun current matches (or my/pdf-isearch-highlight-matches occur-hack-p)))
  (advice-add #'pdf-isearch-hl-matches
              :around #'my/pdf-isearch-hl-matches-controllable-highlight)

  (defvar-local my/pdf-isearch-forward? t)

  (defun my/pdf-isearch-forward (&optional regexp-p no-recursive-edit)
    (interactive "P\np")
    (setq my/pdf-isearch-forward? t)
    (isearch-forward regexp-p no-recursive-edit))

  (defun my/pdf-isearch-backward (&optional regexp-p no-recursive-edit)
    (interactive "P\np")
    (setq my/pdf-isearch-forward? nil)
    (isearch-backward regexp-p no-recursive-edit))

  ;; 下面参数arg,原接口是raw prefix,不是数字prefix,
  ;; 这里保持一致,我不知道怎么反转raw prefix(不用hack的方式,
  ;; 比如操作raw prefix列表),不然my/pdf-isearch-repeat-backward
  ;; 可以简单反转下prefix调用my/pdf-isearch-repeat-backward,而不用写两遍。
  (defun my/pdf-isearch-repeat-forward (&optional arg)
    (interactive "P")
    (setq my/pdf-isearch-highlight-matches t)
    (if my/pdf-isearch-forward?
        (isearch-repeat-forward arg)
      (isearch-repeat-backward arg)))

  (defun my/pdf-isearch-repeat-backward (&optional arg)
    (interactive "P")
    (setq my/pdf-isearch-highlight-matches t)
    (if my/pdf-isearch-forward?
        (isearch-repeat-backward arg)
      (isearch-repeat-forward arg)))

  ;; 搜索超过文档结尾时,直接wrap到文档头开始搜索,不要暂停,
  ;; 如果不这样设置,则搜索超过文档结尾时,暂停的时候,结尾的高亮会不见
  ;; 估计不难修复,但是这样搞更简单。
  (add-hook 'pdf-view-mode-hook
            (lambda () (setq-local isearch-wrap-pause 'no)))

  (defun my/pdf-view-force-normal-state ()
    (interactive)
    (evil-force-normal-state)
    (my/pdf-isearch-cleanup-highlight))

  (advice-add #'pdf-isearch-mode-initialize
              :before
              (lambda (&rest args)
                "在正常使用C-s等进行搜索的时候,重置 `my/pdf-isearch-highlight-matches'。"
                (setq my/pdf-isearch-highlight-matches nil)))

  (defun my/pdf-isearch-mode-cleanup ()
    "按/键搜索,如果搜索成功,则按回车后不要清除高亮,如果搜索失败,则清除高亮。"
    (pdf-isearch-active-mode -1)
    (when (my/isearch-failed?) (pdf-view-redisplay)))
  (advice-add #'pdf-isearch-mode-cleanup
              :override #'my/pdf-isearch-mode-cleanup)

  (evil-define-key 'normal pdf-view-mode-map
    ;; 按/或者?开始向前或者向后搜索并记住搜索方向,
    ;; 按n/N继续向前/向后搜索,
    ;; 按ESC则返回normal state的同时清除搜索高亮
    (kbd "/") #'my/pdf-isearch-forward
    (kbd "?") #'my/pdf-isearch-backward
    (kbd "n") #'my/pdf-isearch-repeat-forward
    (kbd "N") #'my/pdf-isearch-repeat-backward
    (kbd "<escape>") #'my/pdf-view-force-normal-state)
  ;; 或者,如果你有用general.el包的话,可以改用`general-def'定义按键绑定。
  ;; (general-def 'normal 'pdf-view-mode-map
  ;;   "/" #'my/pdf-isearch-forward
  ;;   "?" #'my/pdf-isearch-backward
  ;;   "n" #'my/pdf-isearch-repeat-forward
  ;;   "N" #'my/pdf-isearch-repeat-backward
  ;;   "<escape>" #'my/pdf-view-force-normal-state)
  )

接下来是使用evil-collection包的版本,要避免定义的按键绑定被evil-collection覆盖,改一个地方:

(with-eval-after-load 'pdf-tools
  ;; ...此处省略,和前面一样

  ;; 把下面这段

  ;;  (evil-define-key 'normal pdf-view-mode-map
  ;;    ;; 按/或者?开始向前或者向后搜索并记住搜索方向,
  ;;    ;; 按n/N继续向前/向后搜索,
  ;;    ;; 按ESC则返回normal state的同时清除搜索高亮
  ;;    (kbd "/") #'my/pdf-isearch-forward
  ;;    (kbd "?") #'my/pdf-isearch-backward
  ;;    (kbd "n") #'my/pdf-isearch-repeat-forward
  ;;    (kbd "N") #'my/pdf-isearch-repeat-backward
  ;;    (kbd "<escape>") #'my/pdf-view-force-normal-state)

  ;; 改成下面这段
  (defmacro my/modify-evil-collection-key-bindings (mode &rest body)
    (declare (indent defun))
    (let ((feature-name-var (gensym "feature-name"))
          (mode-var (gensym "mode"))
          (mode-str-var (gensym "mode-str"))
          (hook-fun-name-var (gensym "hook-fun-name-var")))
      `(let* ((,mode-var ,mode)
              (,mode-str-var (symbol-name ,mode-var))
              (,feature-name-var (intern (concat "evil-collection-" ,mode-str-var))))
         (if (featurep ,feature-name-var)
             ;; 在当前evil-collection的实现中,如果feature名对应的文件
             ;; 已经加载,则对应的按键setup函数也已经调用,故在这种情况下,
             ;; 我们可以直接执行body。
             (progn ,@body)
           ;; 否则,添加hook到`evil-collection-setup-hook',
           ;; 判断mode名匹配后执行body。
           (let ((,hook-fun-name-var
                  ;; 这里使用gensym生成hook函数名,由于生成的符号是未intern的,
                  ;; 这会导致evil-collection无法调用我们的hook函数,于是我们需要
                  ;; 提前intern下,同时由于gensym会在符号名后面附加一个全局计数器,
                  ;; 加上我们的函数有前缀 my/ ,因此一般不会产生名称冲突。
                  (intern
                   (symbol-name
                    (gensym (concat "my/modify-evil-collection-key-bindings-"
                                    ,mode-str-var))))))
             ;; 使用defun底层使用的defalias,不然defun不会eval函数名参数。
             (defalias ,hook-fun-name-var
               (lambda (mode keymaps)
                 (when (eq mode ,mode-var)
                   ,@body))
               (concat "修改evil-collection设置的" ,mode-str-var "的按键绑定。"))
             (add-hook 'evil-collection-setup-hook ,hook-fun-name-var))))))

  (my/modify-evil-collection-key-bindings 'pdf
    (evil-define-key 'normal pdf-view-mode-map
      ;; 按/或者?开始向前或者向后搜索并记住搜索方向,
      ;; 按n/N继续向前/向后搜索,
      ;; 按ESC则返回normal state的同时清除搜索高亮
      (kbd "/") #'my/pdf-isearch-forward
      (kbd "?") #'my/pdf-isearch-backward
      (kbd "n") #'my/pdf-isearch-repeat-forward
      (kbd "N") #'my/pdf-isearch-repeat-backward
      (kbd "<escape>") #'my/pdf-view-force-normal-state))
  )
1 个赞