メモの取り方の話

思考のメモなどを取る時、昔々はhowmで書いていて、ある時からorgで書くようになって、最近はdenoteをorgフォーマットで使っている。いずれの形式でも1メモ1ファイルになるので、メモが大量になると管理が大変になる。

howmは自前の検索機構でよいが、orgにした時はdeftを使っていた。deftの仕様はたいへんよいのだが、ピュアEmacs Lispなので、ファイル数が増えるとむちゃくちゃgcが走ってとても反応が悪くなる。

で、xeft

で、2023年の1月から検索にxeftを使っている。これはバックエンドにXapianを使っていて、高速な検索がウリである。正規表現が使えないためmigemoが使えないという欠点はあるが、migemoがないと日本語の検索ができないdeftと違って、日本語を直接入力できるのでそこまで困らない。

……のだが、xeftは一覧表示がそっけない上に、あまりカスタマイズの余地もなく、そこにちょっと不満がある。deftに慣れていたので、ファイルの最終更新日付も一緒に出したいのだ。

で、確認すると、タイトルを抜き出すための関数は登録できるようになっていたので、下記のようなファイル名の前に日付をつけるような関数を作ってxeft-title-functionに設定してみた。

  ;; 一覧にファイルの最終更新日時を入れる
  (defun my/xeft-title-and-date (file)
    "Return the title of FILE with its last modification date."
    (let ((last-mod-date (format-time-string "%Y-%m-%d "
					     (nth 5 (file-attributes file)))))
      (re-search-forward (rx "#+TITLE:" (* whitespace)) nil t)
      (let ((bol (point)) title)
	(end-of-line)
	(setq title (buffer-substring-no-properties bol (point)))
	(if (eq title "")
	    (concat last-mod-date (file-name-base file))
	  (concat last-mod-date title)))))
  (setopt xeft-title-function #'my/xeft-title-and-date)

これでファイル名の前に日付が出るようになった。

あと、検索画面でexcerptが出るのはいいが、2.7行分も出る上にそこにdenoteのfront matterやらorgのoptionsやらが含まれてしまって邪魔臭い1。行数を減らしてかつ不要な情報は出さないようにしたいなと思って、元のxeft–file-excerptをちょっと書きかえて、advice-addでoverrideしてみた。

  (defun my/xeft--file-excerpt (file search-phrase)
    "Return an excerpt for FILE.
  Return (TITLE EXCERPT FILE). FILE should be an absolute path.
  SEARCH-PHRASE is the search phrase the user typed."
    (let ((excerpt-len (floor (* 0.7 (1- (window-width)))))
	  (last-search-term
	   (car (last (split-string search-phrase))))
	  title excerpt)
      (with-current-buffer (xeft--work-buffer)
	(widen)
	(erase-buffer)
	(setq buffer-undo-list t)
	(insert-file-contents file nil nil nil t)
	(goto-char (point-min))
	(setq title (funcall xeft-title-function file))
	(narrow-to-region (point) (point-max))
	;; denoteのfront matterとorgのoptions/startupの最後の行の
	;; 次の行からexcerptに入るようにする
	(when (string-equal (file-name-extension file) "org")
	  (let ((case-fold-search t)
		(options "\\(OPTIONS\\|STARTUP\\|DATE\\|IDENTIFIER\\|FILETAGS\\)"))
	    (goto-char (point-max))
	    (when (re-search-backward (concat "^#\\+" options ":.*$") nil t)
	      (forward-line)
	      (narrow-to-region (point) (point-max)))))
	;; Grab excerpt
	(setq excerpt (string-trim
		       (replace-regexp-in-string
			"[[:space:]]+"
			" "
			(if (and last-search-term
				 (search-forward last-search-term nil t))
			    (buffer-substring-no-properties
			     (max (- (point) (/ excerpt-len 2))
				  (point-min))
			     (min (+ (point) (/ excerpt-len 2))
				  (point-max)))
			  (buffer-substring-no-properties
			   (point)
			   (min (+ (point) excerpt-len)
				(point-max)))))))
	;; Return result
	(list title excerpt file))))
  (advice-add 'xeft--file-excerpt :override #'my/xeft--file-excerpt)

適当なわりに自分の用途ではそれらしくは動いているので、まあよし。

こうやって他人の作ったパッケージでも外からなんとかできちゃうのはemacsの良さであり、emacsのとっつきの悪さでもあるんだろう。


1

いやまあ、検索で絞り込みを始めればマッチした周囲が出てくるので、邪魔なのは最初だけなのだが。