evil-search visual mode下默认把region里的字符用来搜索

虽然evil-search的regex超棒,但是有的时候还是直接region选中了直接搜索方便,比如我想用cgn替换的时候。

效果:

  • 选中region
  • /进入evil-search
  • 选中的字符自动成为搜索字符

网上有isearch 的解决方案,但是没法复制到evil-search。请问有没有人造过轮子了?

(when (evil-visual-state-p)
  (let ((evil-ex-search-history `(,(buffer-substring (region-beginning) (region-end)))))
    (evil-normal-state)
    (evil-ex-search-forward)))

hacky but works。我感觉用处不大啊,用C-r C-w 或者 ivy的C-w方便一些

1 个赞

绑定到/吗?我待会看看能不能写成advice

evil-search 最终也是调用 isearch

差点忘了这回事了……我是这么写的:

  (defun moon-put-region-in-search-history (count)
    "Put region into evil-search history.
COUNT is passed to evil-search command."
    (when (evil-visual-state-p)
      (let ((region (buffer-substring-no-properties
                     ;; evil seems to treat point differently,
                     ;; so I added 1 to end point
                     (region-beginning) (1+ (region-end)))))
        (push region evil-ex-search-history)
        (setq evil-ex-search-pattern (evil-ex-make-search-pattern region))
        ;; (push region evil-ex-s)
        (evil-normal-state)
        )))
  (advice-add 'evil-ex-search-forward :before #'moon-put-region-in-search-history)

@amosbird 谢谢你的指点 :smile:

这行比较关键:

 (setq evil-ex-search-pattern (evil-ex-make-search-pattern region))

更新evil-ex-search-pattern才能让gn指向选中的region。

最终的效果是:

  1. 选中region
  2. /,region自动加入搜索历史
  3. esc退出搜索
  4. cgn开始修改
  5. .重复修改

当然可以吧这个advice做成command绑定到一个键上,就不用/esc按两个键了。

一直以为 evil-ex-searchevil-search 的增强版,没想到是不同的实现。

如果用 evil-search,就可以共享 isearch 的 advice 了。

evil-ex-search支持一些evil的text-object(具体名字不清楚), 比如next-match。

所以想用gnmotion就要用evil-ex-search

1 个赞

visual-state里面(region-end)返回的结果似乎跟emacs下直接运行的不太一样

M-x eval-expression (region-end)返回的point,比我在我的函数里(message (region-end))打印的多一位。

advice 放到 evil-ex-start-search 之前比较好,这样 forward/backward 都能用了。

我这里不需要这一行。

考虑到 region 之后可能还要编辑,对于无需编辑的搜索,可以绑定到 * / #

(define-key evil-motion-state-map "#" #'evil-search-symbol-or-region-backward)
(define-key evil-motion-state-map "*" #'evil-search-symbol-or-region-forward)

...

(defun evil-search-symbol-or-region-backward ()
  (if (evil-visual-state-p)
      ... ;; search region text
    ... ;; search symbol at point))

这个现象我很早就注意到了,算是 evil 的 “feature“ 吧。因为 visual 模式下的 region,其 region-end 位置实际上是光标所在字符之前,我们按 y 能复制到期望的字符,应该是 evil 做了处理。但是 buffer-substring-no-properties 的实现是并不关心 evil 的。

visual 模式下选中了:

    |<------>|
    some-tex[t]

实际上是相当于 insert / emacs 模式下:

    |<----->|
    some-tex|t

要想获得正确的 region,光标位置必须再 forward 一个字符:

    |<------>|
    some-text|

1 个赞

绑定*/#挺好的,不过直接绑定函数没法处理count,比如3#

我还是比较喜欢advice的路线,这样可以处理count:

(defun moon-evil-ex-search-word-backward-advice (old-func count &optional symbol)
  (if (evil-visual-state-p)
      (let ((region (buffer-substring-no-properties
                     (region-beginning) (1+ (region-end)))))
        (setq evil-ex-search-pattern region)
        (deactivate-mark)
        (evil-ex-search-full-pattern region count 'backward))
    (apply old-func count symbol)))

...

(advice-add #'evil-ex-search-word-backward :around #'moon-evil-ex-search-word-backward-advice)

hmm, 你这个需求可以试试 evil-multiedit 。 iedit的 iedit-show/hide-unmatched-lines 第一次用惊艳到我了

evil-multiedit挺不错的,不过多光标有一个问题就是有可能选中多余的文本。用多光标搜索替换经常要手动修改,不如一个个替换方便灵活。

具体可以看这篇文章:

编辑还是要多种手段并用,像 cgn . 这样的操作无法中途跳过一/多个匹配项。

另外,把 region 压入 evil-ex-search-history 也不是很好的方法:

  1. 没有立即搜索。按下 / 的时候,虽然 region 内容出现在搜索提示符后面,但是并没有立即搜索并高亮匹配的项,而是要 esc / enter 退出,再按 n 或其它操作才能看到效果。

  2. 无法编辑关键字。例如当我选中 fooba 进行搜索,发现漏了一个字符,想要补全。一按键就会触发删除的 hook,把 history 清空。

想跳过匹配项的话按n就可以了。

第一条挺有意思的,我可以看看evil是怎么高亮的然后修改一下我的advice,看看能不能实现。

你说的第二点比较难搞,没法用advice/hook添加,我能想到的只有重写evil-ex-search的实现。 如果需要的话可以按C-p自动输入上一个历史再修改。也不算太麻烦。

失察了,n 确实不会影响 . 操作

后边 1、2 那两个问题我这个函数解决了:

(defun evil-ex-start-search-with-region-string ()
 (let ((selection (with-current-buffer (other-buffer (current-buffer) 1)
                   (when (evil-visual-state-p)
                    (let ((selection (buffer-substring-no-properties (region-beginning)
                                      (1+ (region-end)))))
                     (evil-normal-state)
                     selection)))))
  (when selection
   (evil-ex-remove-default)
   (insert selection)
   (evil-ex-search-activate-highlight (list selection
                                       evil-ex-search-count
                                       evil-ex-search-direction)))))

(advice-add 'evil-ex-search-setup :after 'evil-ex-start-search-with-region-string)
1 个赞

没太懂,原版evil / n *里哪些是evil-search,哪些是evil-ex-search?我因为把w b都改成对symbol操作所以*是这个:

(defun jjpandari/evil-search-symbol-forward ()
  "Search forward for symbol under point."
  (interactive)
  (evil-ex-search-word-forward 1 t)
  )

是不是避免了你们这个问题?

虽然直接调用 evil-ex-search-XXX 函数是不会有问题,但 n 和其它操作操作仍然绑定 isearch 行为,相当于临时地执行了一次 evil-ex-search,所以不能连贯地使用 motion,有一点点混乱。

正确的设置是:

(evil-select-search-module 'evil-search-module 'evil-search)

然后 / 搜索,退出,再 gn 就可以跳至下个匹配点,isearch 没这个效果。

选了 evil-search 作为 search-module 了之后,没有参数的 XXX-search-symbol-forward 函数就不能绑定到 */#,会出错。

我是定义了新的函数,相当于抄了一遍 evil-ex-search-word-forward 函数,只把函数最末一句从 symbol 改为固定值 t

(evil-define-motion evil-ex-search-symbol-forward (count &optional symbol)
  "Search for the next occurrence of symbol under the cursor."
  :jump t
  :type exclusive
  (interactive (list (prefix-numeric-value current-prefix-arg)
                     evil-symbol-word-search))
  (evil-ex-start-word-search nil 'forward count t))

(define-key evil-motion-state-map "*" #'evil-ex-search-symbol-forward)
1 个赞