当我们在emacs中进行一项日常工作,一个灵感跑到脑子里。这时,我们通常的做法是:打开自己用来记录灵感的文件,记录想法,切回正在编辑文件,继续当前工作… 尽管在emacs中我们有许多的方法快速打开那个“灵感文件”,比如设置bookmark等,但这个过程仍然容易打断我们的工作流。每一次的buffer切换,光标移动都意味着我们需要为这次临时的记录,多一次选择,多付出一些精力。
这让我开始思考,如何在保留当前编辑区域的情况下,快速记录临时的想法呢。事实上,org-capture 的思路是不错的。但是 org-capture 和 org-mode 绑定,我们需要一个更加通用的 capture 功能,支持任何格式,任意方式的记录。于是便有了 my-capture。
demo
代码 和 配置
代码
(defvar my-capture-return-winconf nil)
(defvar my-capture-buffer "*My Capture*")
(defvar my-capture-target-alist nil)
(defvar my-capture-target-key nil)
(defvar my-capture-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-s") #'my-capture-save)
(define-key map (kbd "C-c C-c") #'my-capture-finalize)
(define-key map (kbd "C-c C-k") #'my-capture-cancel)
map))
(defun my-capture--parse-target (target-info)
(pcase target-info
((pred stringp) (cons target-info 'append))
((pred consp)
(cond
((functionp (car target-info)) (apply (car target-info) (cdr target-info)))
((stringp (car target-info)) target-info)))))
(defun my-capture--write ()
(let* ((target-key my-capture-target-key)
(content (buffer-substring (point-min) (point-max)))
(target-info-lst (cdr (assoc target-key my-capture-target-alist))))
(mapcar (lambda (target)
(let* ((file-pos-cons (my-capture--parse-target target))
(file (car file-pos-cons))
(point-or-symbol (cdr file-pos-cons))
point)
(with-current-buffer (find-file-noselect file)
(save-excursion
(pcase point-or-symbol
('prepend (setq point (point-min)))
('append (setq point (point-max)))
(_ (setq point point-or-symbol)))
(goto-char (or point (point-max)))
(insert content)))))
target-info-lst)))
(defun my-capture-save ()
"Save content to file and continue capturing."
(interactive)
(my-capture--write)
(erase-buffer))
(defun my-capture-finalize ()
"Finalize current capture"
(interactive)
(my-capture--write)
(set-window-configuration my-capture-return-winconf)
(kill-buffer my-capture-buffer))
(defun my-capture-cancel ()
"Cancel current capture."
(interactive)
(set-window-configuration my-capture-return-winconf)
(kill-buffer my-capture-buffer))
(define-minor-mode my-capture-mode
"Minor mode for quick capture."
:keymap my-capture-mode-map
(if my-capture-mode
(setq-local header-line-format
(substitute-command-keys
(format "\\<my-capture-mode-map>Capture %s, save `\\[my-capture-save]', finish \
`\\[my-capture-finalize]', cancel `\\[my-capture-cancel]'." my-capture-target-key)))
(setq-local header-line-format nil)))
(defun my-capture ()
"Pop up a side window to capture text."
(interactive)
(let ((keyword (completing-read "Choose a capture type: " my-capture-target-alist nil t)))
(setq my-capture-return-winconf (current-window-configuration))
(display-buffer-in-side-window (get-buffer-create my-capture-buffer) nil)
(select-window (get-buffer-window my-capture-buffer))
(erase-buffer)
(setq-local my-capture-target-key keyword)
(my-capture-mode 1)))
配置示例
(setq my-capture-target-alist
'(("fleeting-note" (my-capture-note "Fleeting notes"))
("permanent-note" (my-capture-note "Permanent notes"))
("literature-note" (my-capture-note "Literature notes"))
("project-note" (my-capture-note "Project notes"))
("misc-note" (my-capture-note))
("todo" (my-capture-todo))
("inbox" (my-capture-inbox))))
(defvar my-capture-note-file "~/PARA/RESOURCE/Emacs/pkgs/capture/zknote.org")
(defvar my-capture-todo-file "~/PARA/RESOURCE/Emacs/pkgs/capture/zktodo.org")
(defvar my-capture-inbox-file "~/PARA/RESOURCE/Emacs/pkgs/capture/zkinbox.org")
(defun my-capture-note (&optional type)
"TYPE should be one of Fleeting notes, Permanent notes,
Literature notes, Project notes."
(let ((file my-capture-note-file)
(timestamp (format-time-string "** %Y-%m-%d %H:%M:%S"))
point)
(with-current-buffer (find-file-noselect file)
(save-excursion
(goto-char (point-min))
(if type
(when (re-search-forward type nil t)
(end-of-line)
(insert "\n" timestamp "\n")
(setq point (point)))
(when (re-search-forward "Misc" nil t)
(end-of-line)
(insert "\n" timestamp "\n")
(setq point (point))))))
(cons file point)))
(defun my-capture-todo (&optional month-num)
(let ((file my-capture-todo-file)
(month (if month-num
(concat (format-time-string "%Y-")
(string-pad month-num 2 ?0 t))
(format-time-string "%Y-%m")))
point)
(with-current-buffer (find-file-noselect file)
(save-excursion
(goto-char (point-min))
(when (re-search-forward (concat "^* " month) nil t)
(insert "\n** TODO ")
(setq point (point)))))
(cons file point)))
(defun my-capture-inbox ()
(let ((file my-capture-inbox-file)
point)
(with-current-buffer (find-file-noselect file)
(save-excursion
(goto-char (point-min))
(insert "\n")
(setq point (point-min))))
(cons file point)))
配置说明
使用 my-capture 只需要配置 my-capture-target-alist
变量。变量主要支持以下格式:
(setq my-capture-target-alist
'(;; 指定一个文件,默认在文件最后写入capture的内容(2的简写形式)
("target key1" "path/to/file")
;; 指定一个文件,在文件最后写入capture的内容
("target key2" ("path/to/file" . append))
;; 指定一个文件,在文件开头写入capture的内容
("target key3" ("path/to/file" . prepend))
;; 指定一个自定义函数,函数需要返回一个 cons-cell: (文件路径 . 光标位置)
("target key4" (func args))))