最近经常在scratch buffer 中粘贴一段json ,以便将其格式化、或折叠部分json结构,
每次都要手动将其切换到json-ts-mode后 才能使用其中的快捷键 很不爽, 于是研究了
粘贴的时候自动根据粘贴的内容切换到相应的major-mode
代码如下:
另外一个就是每次kill scratch 这个buffer时 我会将其保存到cache目录下,以防万一需要找回之前kill掉的scratch buffer 。 一并放在这,供大家参考。
写这篇文章的时候 突然想到一个管理scratch 的思路(还没实现), 就是将 scratch 与 ~/.cache/emacs/scratch 这个文件进行关联 并用 git 管理这个目录,
每次kill scratch buffer的时候 将其主动写入文件执行 git add;git commit 等命令,
然后就可以利用 git timemachine 等package来遍历 scratch 的历史了。
(defun scratch-auto-set-major-mode (&optional arg)
(when (and (string= (buffer-name) "*scratch*")
(or (eq this-command 'yank)
(eq this-command 'meow-replace)))
(set-auto-mode)))
(advice-add 'yank :after #'scratch-auto-set-major-mode)
(advice-add 'meow-replace :after #'scratch-auto-set-major-mode)
;; 将 *scratch* 内容写入指定目录下的备份文件
(defun backup-scratch ()
(let* ((backup-file (format-time-string "scratch-%Y-%m-%d-%H-%M-%S.el"))
(backup-path (concat my-scratch-backup-directory backup-file)))
(with-current-buffer "*scratch*"
(write-region (point-min) (point-max) backup-path nil 'silent))
backup-file))
;; 删除指定目录下 3 天前的临时文件
(defun delete-old-scratch-backups ()
(let ((max-age (* 3 24 60 60)) ; 3 天的时间戳
(now (float-time)))
(dolist (file (directory-files my-scratch-backup-directory t "scratch-.*\\.el$"))
(when (and (file-regular-p file)
(> (- now (float-time (nth 5 (file-attributes file))))
max-age))
(delete-file file)
(message "Deleted old scratch backup: %s" file)))))
;; 定时执行删除任务
(run-with-timer 0 (* 24 60 60) #'delete-old-scratch-backups)
(defun vmacs-kill-buffer-dwim(&optional buf)
(interactive)
(with-current-buffer (or buf (current-buffer))
(cond
((equal (buffer-name) "*scratch*")
(backup-scratch)
;; (copy-region-as-kill (point-min)(point-max))
(kill-this-buffer))
((and (featurep 'server)
(boundp 'server-buffer-clients)
server-buffer-clients)
(save-buffer)
(server-edit))
((derived-mode-p 'eshell-mode 'term-mode 'shell-mode 'vterm-mode)
(kill-this-buffer)
;; (vterm-toggle-hide )
;; (vmacs-add-to-killed-file-list)
;; (centaur-tabs-forward-group)
;; (centaur-tabs-backward-group)
)
((equal major-mode 'gnus-group-mode)
(gnus-group-exit))
((equal major-mode 'gnus-summary-mode)
(gnus-summary-exit))
((equal major-mode 'gnus-article-mode)
(gnus-summary-update-info))
((equal major-mode 'magit-status-mode)
(magit-mode-bury-buffer 16))
( (derived-mode-p 'magit-mode)
(let ((process (magit-section-value-if 'process)))
(if (and process
(eq (process-status process) 'run))
(magit-mode-bury-buffer)
(magit-mode-bury-buffer 4))))
( (derived-mode-p 'org-agenda-mode)
(dolist (f org-agenda-files)
(kill-buffer (get-file-buffer f)))
(kill-this-buffer)
)
( (derived-mode-p 'reb-mode)
(call-interactively 'reb-quit))
( (derived-mode-p 'calc-mode)
(call-interactively 'calc-quit))
( (derived-mode-p 'mu4e-view-mode)
(call-interactively 'mu4e-view-quit))
( (derived-mode-p 'xwidget-webkit-mode)
(call-interactively 'kill-this-buffer))
( (or (derived-mode-p 'special-mode)
(string-prefix-p "*vc-git" (buffer-name ))
(derived-mode-p 'compilation-mode))
(if (get-buffer-process (current-buffer))
(bury-buffer-and-window)
(kill-buffer-and-window)))
(t
(message "kill buffer %s" (buffer-name ))
(kill-this-buffer)))))
4 个赞
rua
3
我平时用 scratch 也很多,我记得还有人在放弃用 emacs 的功能后因为非常想念 scratch buffer 还单独做了个产品
1 个赞
实现了上面提到的用git 管理*scratch* 的思路,即将 *scratch* 与 ~/scratch.el 这个文件进行关联,然后git管理~/scratch.el 这个文件。
之所以突然想到用git 管理,是因为 平时我已经在M-n
M-p
绑定到我的另外两个命令
vcgit-next-revision
vcgit-prev-revision
用于快速查看某个文件的历史版本
现在 scratch 即然也是file 了,自然也可以用git 管理后 也利用上git的强大功能。
之所以所择放到 ~/ 下 是希望 *scratch* 的default-directory 是home 目录,使用起来相对方便
但home 目录相对很大,用git 进行管理这么大的目录 会很慢, 不过可以通过默认忽略所有文件的方式来解决这个问题
~/.git/info/exclude
中加入 "*"
即可
我平时都是通过vmacs-kill-buffer-dwim
这个命令来kill-buffer
,只需要对*scratch* 特殊处理一下即可。
如此 想切到*scratch* 时 ,只需要用自带的scratch-buffer
命令,
kill-buffer 时 处理git commit 操作。
想看*scratch* 历史,仍然是平时用到的M-n M-p ,没有太多使用上的心智负担,
也不用占用额外的按键
(global-set-key "\C-x\C-v" 'scratch-buffer)
(global-set-key (kbd "M-p") 'vcgit-prev-revision)
(global-set-key (kbd "M-n") 'vcgit-next-revision)
(setq-default
inhibit-startup-screen t;隐藏启动显示画面
initial-scratch-message nil;关闭 scratch 消息提示
;; initial-major-mode 'emacs-lisp-mode ;scratch init mode
;; initial-buffer-choice t ;默认打开 scratch buffer
;; initial-buffer-choice "~/*scratch*"
initial-major-mode #'(lambda()(emacs-lisp-mode)
(setq buffer-file-name "~/scratch.el")
(setq default-directory "~/")
(setq-local write-contents-functions #'scratch-write-contents))
)
(defun scratch-write-contents ()
;; 避免autosave总是提醒是否真的保存
(with-current-buffer "*scratch*"
(let ((txt (buffer-string)))
(when buffer-file-name
(require 'vc-git)
(unless (file-exists-p "~/.git")
;; git init ad ~
(vc-git-create-repo)
(with-temp-buffer
(insert "*") ;default ignore all files in ~
(append-to-file (point-min)(point-max) "~/.git/info/exclude")))
(save-window-excursion
(with-temp-file buffer-file-name
(insert txt))
(set-buffer-modified-p nil)
(vc-git-register `(,(buffer-file-name)))
(vc-git-command nil 'async buffer-file-name
"commit" "-m" "autosave" "-q"
))))))
(defun vmacs-kill-buffer-dwim(&optional buf)
(interactive)
(with-current-buffer (or buf (current-buffer))
(cond
((equal (buffer-name) "*scratch*")
(save-buffer)
(kill-this-buffer))
((and (featurep 'server)
(boundp 'server-buffer-clients)
server-buffer-clients)
(save-buffer)
(server-edit))
((derived-mode-p 'eshell-mode 'term-mode 'shell-mode 'vterm-mode)
(kill-this-buffer)
;; (vterm-toggle-hide )
;; (vmacs-add-to-killed-file-list)
;; (centaur-tabs-forward-group)
;; (centaur-tabs-backward-group)
)
((equal major-mode 'gnus-group-mode)
(gnus-group-exit))
((equal major-mode 'gnus-summary-mode)
(gnus-summary-exit))
((equal major-mode 'gnus-article-mode)
(gnus-summary-update-info))
((equal major-mode 'magit-status-mode)
(magit-mode-bury-buffer 16))
( (derived-mode-p 'magit-mode)
(let ((process (magit-section-value-if 'process)))
(if (and process
(eq (process-status process) 'run))
(magit-mode-bury-buffer)
(magit-mode-bury-buffer 4))))
( (derived-mode-p 'org-agenda-mode)
(dolist (f org-agenda-files)
(kill-buffer (get-file-buffer f)))
(kill-this-buffer)
)
( (derived-mode-p 'reb-mode)
(call-interactively 'reb-quit))
( (derived-mode-p 'calc-mode)
(call-interactively 'calc-quit))
( (derived-mode-p 'mu4e-view-mode)
(call-interactively 'mu4e-view-quit))
( (derived-mode-p 'xwidget-webkit-mode)
(call-interactively 'kill-this-buffer))
( (or (derived-mode-p 'special-mode)
(string-prefix-p "*vc-git" (buffer-name ))
(derived-mode-p 'compilation-mode))
(if (get-buffer-process (current-buffer))
(bury-buffer-and-window)
(kill-buffer-and-window)))
(t
(message "kill buffer %s" (buffer-name ))
(kill-this-buffer)))))
;;;###autoload
(defun vcgit-next-revision ()
"Visit the next blob which modified the current file."
(interactive)
(let* ((buffname (buffer-name))
(prev-buffer (current-buffer))
(parent-buffer vc-parent-buffer)
(fileset-arg (vc-deduce-fileset nil t))
(backend (car fileset-arg))
(filename (car (cadr fileset-arg)))
rev next)
(when parent-buffer
(if (bound-and-true-p vc-buffer-revision)
(setq rev vc-buffer-revision)
(when (string-match "^\\([^~]+?\\)\\(?:\\.~\\([^~]+\\)~\\)?$" buffname)
(setq rev (match-string 2 buffname))))
(setq next (vc-call-backend backend 'next-revision
filename rev))
(kill-buffer prev-buffer)
(if next
(switch-to-buffer (vc-find-revision filename next))
(find-file filename)
(user-error "vcgit timemachine: You have reached the end of time")))))
;;;###autoload
(defun vcgit-prev-revision ()
"Visit the prev blob which modified the current file."
(interactive)
(let* ((buffname (buffer-name))
(cur-buffer (current-buffer))
(parent-buffer vc-parent-buffer)
(fileset-arg (vc-deduce-fileset nil t))
(backend (car fileset-arg))
(filename (car (cadr fileset-arg)))
rev prev)
(if (bound-and-true-p vc-buffer-revision)
(setq rev vc-buffer-revision)
(when (string-match "^\\([^~]+?\\)\\(?:\\.~\\([^~]+\\)~\\)?$" buffname)
(setq rev (match-string 2 buffname))))
(if rev
(setq prev (vc-call-backend backend 'previous-revision
filename rev))
(setq prev (vc-working-revision filename)))
(if prev
(progn (switch-to-buffer (vc-find-revision filename prev))
(when parent-buffer (kill-buffer cur-buffer)))
(user-error "vcgit timemachine: You have reached the beginning of time"))))
之所以所择放到 ~/ 下 是希望 scratch 的default-directory 是home 目录,使用起来相对方便 但home 目录相对很大,用git 进行管理这么大的目录 会很慢, 不过可以通过默认忽略所有文件的方式来解决这个问题
~/.git/info/exclude
中加入 "*"
即可
这样的思路有一些过于 hackish 了,而且把 HOME 目录设置成 .git 很容易有意想不到的副作用。因为很多应用都会 assume .git 是 project-root,这样你等于把你的 HOME 目录当成你的 project-root 了,这样对于你真正的 git project 就会形成嵌套 project。容易有意想不到的意外情况。
你可以在创建 scratch buffer 的时候写一个函数或者 advice 动态绑定 default-directory 为你的 HOME 目录。另外就是你可以把你的 scratch.el 的真实目录放在一个 cache 目录底下,然后 ~/scratch.el
可以是一个 symlink link 到你位于 cache 目录底下的 scratch.el文件里。
1 个赞
他这个确实不好. 不过 Git 目录是可以手动指定的, 不一定得是 .git
.
emacs启动时, 自动从~/.emacs.scratch文件加载内容到 *scratch*
emacs退出时, 将*scratch*内容保存到~/.emacs.scratch中
如果*scratch*内容有变化, 在idle time保存到~/.emacs.scratch
如果*scratch*内容没有变化, 每隔几分钟尝试保存, 没有变化时, 检查间隔不断变大