交换两个 Region 的内容(利用 Recursive Editing)

大家一定都用过 (elisp) Recursive Editing 这个功能了,比如 M-x toggle-debug-on-error 后进入 Debugger 就会用到它:

但有可能像我一样一直都并不了解它,Emacs 一次执行一个命令,Recursive Editing 可以让用户在执行一个命令期间暂时跳出这个命令,用户回到正常的 Emacs,最后再用 C-M-c (exit-recursive-edit) 返回这个命令。比如 M-x ediff-regions-wordwise 就是这样工作的。

利用这个机制,很容易解决标题中的需求:

(defun chunyang-swap-regions ()
  (interactive)
  (let ((hint
         (substitute-command-keys
          "When done, type \\[exit-recursive-edit]. Use \\[abort-recursive-edit] to abort"))
        buf-A reg-A-beg reg-A-end reg-A-str
        buf-B reg-B-beg reg-B-end reg-B-str)
    ;; Select the first region
    (message "Select the first region (%s)" hint)
    (recursive-edit)
    (setq buf-A (current-buffer)
          reg-A-beg (region-beginning)
          reg-A-end (region-end)
          reg-A-str (buffer-substring reg-A-beg reg-A-end))
    (deactivate-mark)
    ;; Select the second region
    (message "Select the second region (%s)" hint)
    (recursive-edit)
    (setq buf-B (current-buffer)
          reg-B-beg (region-beginning)
          reg-B-end (region-end)
          reg-B-str (buffer-substring reg-B-beg reg-B-end))
    (deactivate-mark)
    ;; Swap these two regions
    (when (< reg-B-beg reg-A-beg)
      (cl-psetq buf-A buf-B
                reg-A-beg reg-B-beg
                reg-A-end reg-B-end
                reg-A-str reg-B-str
                buf-B buf-A
                reg-B-beg reg-A-beg
                reg-B-end reg-A-end
                reg-B-str reg-A-str))
    (with-current-buffer buf-B
      (delete-region reg-B-beg reg-B-end)
      (goto-char reg-B-beg)
      (insert reg-A-str))
    (with-current-buffer buf-A
      (delete-region reg-A-beg reg-A-end)
      (goto-char reg-A-beg)
      (insert reg-B-str))))

很久以前我用 Minor Mode 解决过,大概是实现了类似 Recursive Editing 的流程,结果一个小小的需求写了一堆乱糟糟的代码,最后随性不用了。

2 个赞

就是解决需要选择两个区域的问题吧。

是的,对解决我的这个需求来说是起到这个作用,可以算作 Recursive Editing 的一个应用。query-replaceC-r 也用到了,虽然我不清楚有什么必要。

那么似乎轮替三个选择区域的内容是做不到的,虽然应该没人会有这种需求。

https://github.com/wence-/elisp/blob/master/multi-region.el

题外话,现在越来越觉得觉得 Emacs 没有命名空间是个挺大的问题。

也可以先后 mark 两个区域,记下起止位置并设置高亮假装两个 region,然后交换。

emacs不是支持第二选区吗?mac下是按下alt键不松,然后用鼠标左键选区域

(emacs) Secondary Selection

可能是受到一些插件的影响,我用鼠标选完之后光标会乱跳,结果老是选不到要选的东西。

比起「两个 Region」这个少见的问题,这里更有趣的是 Recursive Editing 这个概念,大家不知不觉都用过了,但很可能并不清楚它。

选择两个区域,这个牵涉的用户操作太多,感觉有很多异常情况要处理,比如用户中间修改了文件内容,修改了第一个区域内容,切换了buffer,两个区域有交错或者覆盖等等

每个区域的前后位置是不是应该用marker保存?否则第一个替换之后,第二个区域位置可能变化

先替换第二个,第一个又不会变

异常情况只要开个只读就解决了。

两个区域不在一个buffer,两个区域交错或覆盖。

只读还需要跟踪用户操作过程中是否取消了只读,修改了文件等情况

The Arise of Worse is Better

让用户自己鉴定操作是否合法。取消只读很明显不会是误操作。

操作系统都没考虑过陨石撞地球要怎么处理。

至于选择区重叠的测试也很好写,比对一下第二个区域的 beg 是不是在第一个的中间就行。发现重叠以后直接 abort

至于不在一个缓冲区怎么处理,上面的代码已经考虑到了。

1 个赞

跟踪用户操作很常见啊,自动取消就行了,这就彗星撞地球了,你的标准真低。

这种函数只能个人用

Talk is cheap. Show me you big XX.

evil-exchange是这么做的:

添加hook到after-change-functions, 检测到文件修改, 自动取消并提示用户, 岂不比你的readonly体验好? 还可以精确判断修改的地方是否影响区域. 这点功能就彗星撞地球了?

XX是什么?

这个跟我下午想到的一个方案一模一样, 不过这个问题是,用户调用了第一次之后, 可能会被什么事情打扰, 跳转到其他地方, 把这个东西忘记了, 然后过了很久, 要从头开始操作, 实际执行的却是第二步, 有点意外了.

所以完善的做法是两个命令, 一个执行第一步, 另一个执行第二步, 这样不会出意外.

加钩子的性能开销,我认为是得不偿失的。

不然我也想用 OOP 把所有功能重构一遍,以后什么操作要额外发个消息做个动作改起来更容易。

又不是用 C++。

这个担心多虑了, 没什么开销, 而且只是这个操作启动时加上, 结束去掉. 本来就是给你这种情况用的, 反倒嫌浪费, 那还有什么可用?