总结了一下自动保存的姿势

之前我一直在用 @manateelazycatauto-save。很爽,但是用了一年多之后,觉得某些情形下还是太偏激了。
比如

  1. yasnippet 展开的时候,此时自动保存的时候就会打断 yasnippet。
  2. 开着类似 npm watch 的时候,自动保存会频繁触发自动编译。有时候你敲到一半停下来,代码肯定是无法通过编译的,然后就报警。。。

最近尝试了一下 super-save,这个主要是在失去焦点的时候保存,更可控一点。而且它也有在 idle 的时候保存的选项。用了一段时间还挺好。
问题是它保存的时候仅仅保存的是当前 buffer,不会保存所有被修改的文件。
有人(@JJPandari )在这个 issue 提到过这个问题,而且也有人提了 PR
作者口口声声说没必要,理由是当前的 buffer 已经被保存了为啥还要保存所有呢?不过其实还是会出现这种情况的。
比如在用 ivy-occur 的时候就会出现这种情况,虽然可以通过添加 wgrep-auto-save-buffert 来直接保存所有的文件, 但是在我平常的使用中,还是会出现有不在当前 buffer 的文件被修改的情况。虽然我不知道什么条件下触发的。

所以,参照这个 PR 自己先 hack 一下。

(defun save-all-buffers ()
  (save-excursion
    (dolist (buf (buffer-list))
      (set-buffer buf)
      (when (and buffer-file-name
                 (buffer-modified-p (current-buffer))
                 (file-writable-p buffer-file-name)
                 (if (file-remote-p buffer-file-name) super-save-remote-files t))
        (save-buffer)))))

(advice-add 'super-save-command :override 'save-all-buffers)

或者如果你想在保存的时候不提示保存了哪些文件,也可以替换成 auto-save 里的那个保存函数,里面有提供这个功能。


接下来的问题是保存的时候自动删除多余的空格。
delete-trailing-whitespace 的问题是会把当前光标所在位置的行尾空白也干掉,有时候你在缩进的时候保存一下就很烦。
这个在 auto-save 中已经有实现了,把它单独拿出来也很简单,就 before-save-hook 挂一下就好。
但是还存在一个问题,写 markdown 的时候,起到换行作用的行尾空格也会被删除,这个比较烦人,于是乎可以改一下。

;;; whitespace-killer --- 删除多余的空白字符
;;; Commentary:
;;; 删除行尾空白字符但不删除 Markdown 格式的行尾空白字符
;;;
;;; TODO Markdown 删除非行尾的单行空白字符
;;;
;;; Code:

;; https://www.emacswiki.org/emacs/DeletingWhitespace#toc13
(defun whitespace-killer-delete-end-trailing-blank-lines ()
  "Deletes all blank lines at the end of the file."
  (interactive)
  (save-excursion
    (save-restriction
      (widen)
      (goto-char (point-max))
      (delete-blank-lines))))

;; https://www.emacswiki.org/emacs/auto-save.el
(defun whitespace-killer-delete-trailing-whitespace-except-current-line ()
  "Delete trailing whitespace except current line."
  (interactive)
  (let ((begin (line-beginning-position))
        (end (line-end-position)))
    (save-excursion
      (when (< (point-min) begin)
        (save-restriction
          (narrow-to-region (point-min) (1- begin))
          (delete-trailing-whitespace)))
      (when (> (point-max) end)
        (save-restriction
          (narrow-to-region (1+ end) (point-max))
          (delete-trailing-whitespace))))))

(defun kill-whitespace ()
  "Kill whitespace."
  (interactive)
  (if (derived-mode-p 'markdown-mode)
      (whitespace-killer-delete-end-trailing-blank-lines)
    (whitespace-killer-delete-trailing-whitespace-except-current-line)))

(provide 'whitespace-killer)
;;; whitespace-killer.el ends here

然后 (add-hook 'before-save-hook 'kill-whitespace) 即可。
这样在 Markdown 及其子模式下,都只是删除文件末尾多余的空白字符,不删除行尾空白字符。


如果和你一起工作的人都不清理空格,那么你的 commit 就会充斥着各种删除空格的 diff,这个时候可以尝试一下不那么激进的 ws-butler,spacemacs 好像也用的这个。最近正在尝试使用,很不错,只会删除自己编辑过的多余空格,而不会删除打开文件的时候就已经存在的多余空格。
这个工具还有个很有意思的地方,就是删除当前行所在的缩进空格时,会留下 “虚拟空格”,而并不像上面我们做的那样仅仅是跳过当前行。也就是说,实际上硬盘上已经删除了缩进空格,但是 Emacs 里显示的还是有缩进。为了证明这一点,你可以再打开一个别的编辑器看看空格是不是真的删除了。(我试了,确实删除了,很神奇。:joy:


剩下的问题是,Markdown 如何删除段落中多余的空白字符,比如空行的空白字符。以及结尾超过两个的空格的空白字符

aaa[空格][空格][空格]
[空格][空格][空格]
bbb

不过这种需求目前还没有那么强烈,以上的方法结合起来之后已经完全满足自己自动保存的需求了。

7 个赞

我就是那个提pr的,哈哈哈。

希望作者可以合并吧,毕竟默认是关闭的,不破坏用户习惯

3 个赞

auto-save-visited-mode 如何,也会打断吗

这个我没尝试过啊,是直接保存当前文件?不是保存到另一个井号开头的文件么?

  • super-save一个个存,从各个角度看都不如在某个时刻一下子全存了,这里你也是这么替换save-command的。
  • super-save干了一大堆事情,我自己idle-timer+focus-out-hook来那么一下,感觉用起来也没什么区别 :rofl: ,然后发现了evil-write-all,正好把lazycat的save-all函数也扔了
  • 自动删trailing space会多出git diff,反正我不用,又少一件事哈哈哈哈耶 :rofl:
  • 本来想把idle设为30秒,主要靠focus-out-hook,后来发现用惯了lazycat的save,1秒后没有自动存完全不习惯,就又改回1秒了。

现在是这样:

(defvar jester-auto-save-idle 1 "Time in seconds before auto-saving all buffers.")
(run-with-idle-timer jester-auto-save-idle t #'jester/save-all-buffers)
;; (cancel-function-timers 'jester/save-all-buffers) ;; for debugging
(add-hook 'focus-out-hook #'jester/save-all-buffers)

;;----------------------------------------------------------------------------
;; Save all buffers.
;;----------------------------------------------------------------------------
(defun jester/save-all-buffers ()
  "Save all buffers."
  (evil-write-all nil))
3 个赞

auto-save-visited-mode 是直接保存到当前文件 但是你可以通过开启文件版本功能保存历史版本到你指定的路径

;;启用版本控制,即可以备份多次 (setq version-control t) ;;(setq backup-by-copying t) ;;备份最原始的版本0次,记第一次编辑前的文档,和第二次编辑前的文档 (setq kept-old-versions 0) ;;备份最新的版本1次,理解同上 (setq kept-new-versions 1) ;;删掉不属于以上8版本的版本 (setq delete-old-versions t) ;;设置备份文件的路径 (setq backup-directory-alist '(("." . “/mnt/c/*****/.saves”)))

;;自动保存会保存到访问的文件内而不是#文件名#

(auto-save-visited-mode 1) (setq auto-save-timeout 3)

1 个赞

来了来了, 新姿势来了.

今天折腾了一下auto-save, 顺带把几个坑给解决了. 先上代码

主要修复了一下几个问题

  1. 执行undo-tree-visualize时, 如果auto-save自行保存正在做undo的buffer, undo-tree-visualize会被打断, 想想你正在undo一个文件, diff还没看清呢, 然后undo-tree就自动关闭了, 还多出来几个没有什么用的frame. 体验极差. Solution: 让auto-save自动跳过undo-tree正在执行的buffer, undo-tree用变量undo-tree-visualize-parent-buffer保存他的父buffer, 我们简单做个eq比对,然后排除就完事了. 当然, 要在undo-tree-visualize前强制保存一下. 这样才万无一失了.

  2. 快速的auto-save打断eldoc, 保存文件对echo area的输出不基于message函数, 而是交由更底层的实现来完成(save-silently就是用一段空信息盖住原本输出的信息), 因此我们flet或者advicemessage是没有用的. 我目前的方法是保存完调用eldoc-print-current-symbol-info重新显示eldoc, 代价就是会闪烁一下echo area, 如果谁有更好的方案, 可以提出来众乐乐.

2 个赞

懒猫的原版 auto-save 里用的是 (with-temp-message "" (basic-save-buffer)),经测试不会覆盖 eldoc。但是一样会闪烁一下。

thx, it works.

闪烁的问题基本无解,除非重写write-region函数。老实说这加上awesome-tray简直闪死人了,老王自己用不觉得闪吗? @manateelazycat

我写了一个补丁, 这样就不会闪烁了 https://github.com/manateelazycat/lazycat-emacs/commit/da13a688ef89f8ab2c577a3e9d2a7bcf0ef9b71d

(with-temp-message
  (with-current-buffer " *Minibuf-0*" (buffer-string))
    (basic-save-buffer))
3 个赞

眼睛没那么敏感, anyway 看我上面的解决方案

可以,姜还是老的辣

我有那么老吗? :sob:

4 个赞

我说我的eldoc总是过一两秒就不见了,还发了个帖子来着……因为整个配置都是新的,也不知道是哪里出了问题,原来是用evil-write-all替代了lazycat的自动保存搞出来的……


另外,lazycat的是否保存的判断是有file-name和modified,evil-write-all是file-name和not readonly @manateelazycat 你看这个readonly有必要不。哦modifiled就肯定不是readonly了。另外后者用了save-some-buffers ,看起来不错,除了不能统计存了几个文件。我随手贴一下它的代码吧:

(save-some-buffers t
                       #'(lambda ()
                           (and (not buffer-read-only)
                                (buffer-file-name))))

不用,我的代码遇到这种的会直接跳过

发现一个问题,我的yas的自动跳转field时好时坏好久了,突然发现和eldoc消失一样,也是自动保存导致的,snippet展开后我按快一点,就不会有问题,慢了,出了自动保存,就无法next-field了。

仿照我的代码,让auto-save跳过有yas展开的buffer吧

改了,加了个:

;; yas overlay and company-select-next has problem with this.
(when (and (not yas--active-snippets) (not company-candidates))
  ...)

没把predicate抽出来,也没防御yas和company这两个变量不存在的问题,我是自己用这个函数而且无论怎么配这两个包一定会用。

可以写成(unless (or )),短一点。

1 个赞

我的大脑只能理解if not,理解不了unless。。。

2 个赞