indent-yank:根据光标位置在粘贴时自动缩进

最近使用 JB家的IDE时发现粘贴后可以自动缩进,很方便: jb 网上找了一圈,感觉 Emacs 上实现的效果个人感觉都不太理想,于是自己写了一个,目标是足够通用,所有 major-mode 和所有与 yank 有关的命令(比如 consult-yank-pop) 都可以使用。先看效果:

  • org-mode
    org
  • prog-mode
    py

最后贴上代码 indent-yank.el

(require 'dash)

(defun indent-yank-line-empty-p ()
  (save-excursion
    (beginning-of-line)
    (looking-at-p "\\_>*$")))

(defun indent-yank-remove-indent-in-string (str)
  (with-temp-buffer
    (insert str)
    (goto-char (point-min))
    (let ((indent most-positive-fixnum))
      (while (not (eobp))
        (when (not (indent-yank-line-empty-p))
          (setq indent (min indent (current-indentation))))
        (forward-line 1))
      (goto-char (point-min))
      (while (not (eobp))
        (when (>= (current-indentation) indent)
          (beginning-of-line)
          (delete-forward-char indent))
        (forward-line 1)))
    (buffer-string)))

(defun indent-yank-yank (&optional arg)
  (interactive "*P")
  (let* ((arg (or arg 1))
         (indent (current-indentation))
         (text-without-indent (indent-yank-remove-indent-in-string (current-kill 0)))
         (text-yank (replace-regexp-in-string
                     "\n" (concat "\n" (-repeat indent ? )) text-without-indent)))
    (let ((head (nth (- arg 1) kill-ring)))
      (setf (nth (- arg 1) kill-ring) text-yank)
      (yank arg)
      (setf (car kill-ring) head))))

(defun indent-yank-before-insert-for-yank (args)
  (if indent-yank-mode (list (replace-regexp-in-string "\n" (concat "\n" (-repeat (current-indentation) ? )) (indent-yank-remove-indent-in-string (car args))))
    args))

(advice-add #'insert-for-yank :filter-args #'indent-yank-before-insert-for-yank)

(define-minor-mode indent-yank-mode
  "Minor mode for yanking based on indentation at point.")

(provide 'indent-yank)
1 个赞

aggressive-indent 不能满足需求吗?

agressive-indent 我用过,不过缺点比较明显:

  1. 代码多的时候性能开销太大,大部分情况下在粘贴时进行自动缩进就足够了
  2. 取决于对当前语言缩进预测的准确程度,如果预测不准确(特别是 Python 这类有缩进语义的语言)就不太好用了
  3. 容易破坏代码结构,比如一些有意留下的缩进
1 个赞

其实想做到这种效果只需要 yank 后按下 C-x C-x TAB,transient-mark-mode 下 C-M-\ 也可以

1 个赞

C-x C-x TAB 其实就依赖于对当前语言缩进的预测准确度了,像 Python 的各种语句块还有 Rust 的宏里,缩进预测往往并不准确

这是各别 minor mode 的问题,如果 python mode 下无法使用 indent-region,因该向 [email protected] 提交报告。

其实是缩进预测不准确的问题,这个不是 bug ,很多时候机器是没法理解我们的意图的,比如: py

如果 indent-line 能正确缩进,indent-region 按道理也可以,因此我认为这是 python-mode 的 bug。

python和haskell这种语言不管是行还是块缩进,都存在多种缩进语意切换的情况,和c类语言不一样,往往只有一种缩进语意。

1 个赞

可是 indent-region 本身就是 dwim 类的命令,最好还是报告一下吧。

这个不是 mode 本身缩进实现的问题,因为这些靠空格缩进的语言在一些特殊情况下,不同缩进都是正确的语义。

任何技术都没法猜对用户到底是要整体缩进多少,特别是楼主所说的外部粘贴代码这种情况。

所以,我自己在Python、Haskell这两种语言都仅禁用了自动或全局缩进,indent-line 还可以让用户选择一下, indent-region 几乎不可能选择对,这些不是bug。

这种情况下我认为 indent-region 可以按照 mark 的位置来判定缩进深度。

Elisp端确实可以这样做,用户可以提前把光标移动到正确的缩进列再粘贴。

这个对python-mode缩进增强很不错,python缩进问题,我一直都是手动( :joy:),其他语言,aggressive-indent还行。

python 代码如果 复制时mark没有顶格或者没有复制换行符,缩进计算会错

这个其实是没有什么好的解决办法,不顶格的话不好猜测第一行正确的缩进