Skip to content
Howard Melman edited this page Sep 14, 2021 · 2 revisions

This package defines a command hrm-notes that uses consult to search directories to open files. The idea is to treat a collection of disparate directories as collections of text notes by a topic. Each directory is a consult--multi source associated with a category name and narrow key. By default hrm-notes uses all files but consult's narrowing can be used to isolate files in just one directory. The code opens the file in markdown-mode but the :action can be changed to find-file and then directories of files of any kind can be used.

A challenge is to display just the filename without the distracting full directory path while still enabling commands to find the file in the right directory. The somewhat hacky solution is to use the 'invisible text-property on the directory portion of the full filename. The misfeature in this approach is that the directory portion is still used to filter against during selection.

The display of notes includes annotations using the :annotate keyword of consult--multi instead of marginalia. This was necessary because marginalia didn't have access to the source name (category) which I wanted to include.

Finally this includes embark integration. The category is used as an embark type which uses a keymap that inherits from embark-file-map. This means in addition to the commands defined here, embark's file commands work on notes too. Since the notes use a full path with the directory portion invisible the embark commands can just treat them as normal full paths.

For some design issues faced see Issue 210

To use it yourself just change the data (directories, names and keys) in hrm-notes-sources-data then bind hrm-notes to some key, I use super-o:

(global-set-key (kbd "s-o") 'hrm-notes)

Code:

(require 'consult)
(require 'marginalia)			; for faces
(require 'embark)

(defvar hrm-notes-category 'hrm-note
  "Category symbol for the notes in this package.")

(defvar hrm-notes-history nil
  "History variable for hrm-notes.")

(defvar hrm-notes-sources-data
  '(("Restaurants"   ?r "~/Dropbox/Restaurants/")
    ("Lectures"      ?l "~/Dropbox/Lectures/")
    ("Simplenotes"   ?s "~/Library/Application Support/Notational Data/")))

(defun hrm-notes-make-source (name char dir)
  "Return a notes source list suitable for `consult--multi'.
NAME is the source name, CHAR is the narrowing character,
and DIR is the directory to find notes. "
  (let ((idir (propertize (file-name-as-directory dir) 'invisible t)))
    `(:name     ,name
      :narrow   ,char
      :category ,hrm-notes-category
      :face     consult-file
      :annotate ,(apply-partially 'hrm-annotate-note name)
      :items    ,(lambda () (mapcar (lambda (f) (concat idir f))
				    ;; filter files that glob *.*
				    (directory-files dir nil "[^.].*[.].+")))
      :action   ,(lambda (f) (find-file f) (markdown-mode)))))
      ;; :action   find-file)))  ; use this if you don't want to force markdown-mode

(defun hrm-annotate-note (name cand)
  "Annotate file CAND with its source name, size, and modification time."
  (let* ((attrs (file-attributes cand))
	 (fsize (file-size-human-readable (file-attribute-size attrs)))
	 (ftime (format-time-string "%b %d %H:%M" (file-attribute-modification-time attrs))))
    (put-text-property 0 (length name) 'face 'marginalia-type name)
    (put-text-property 0 (length fsize) 'face 'marginalia-size fsize)
    (put-text-property 0 (length ftime) 'face 'marginalia-date ftime)
    (format "%15s  %7s  %10s" name fsize ftime)))

(defun hrm-notes ()
  "Find a file in a notes directory."
  (interactive)
  (let ((completion-ignore-case t))
    (consult--multi (mapcar #'(lambda (s) (apply #'hrm-notes-make-source s))
                            hrm-notes-sources-data)
                    :prompt "Notes File: "
                    :group nil
                    :history 'hrm-notes-history)))

;;;; embark support

(defun hrm-notes-dired (cand)
  "Open notes directory dired with point on file CAND."
  (interactive "fNote: ")
  ;; dired-jump is in dired-x.el but is moved to dired in Emacs 28
  (dired-jump nil cand))

(defun hrm-notes-marked (cand)
  "Open a notes file CAND in Marked 2."
  (interactive "fNote: ")
  ;; Marked 2 is a mac app that renders markdown
  (call-process-shell-command (format "open -a \"Marked 2\" \"%s\"" (expand-file-name cand))))

(defun hrm-notes-grep (cand)
  "Run consult-ripgrep in directory of notes file CAND."
  (interactive "fNote: ")
  (consult-ripgrep (file-name-directory cand)))

(embark-define-keymap hrm-notes-map
  "Keymap for Embark notes actions."
  :parent embark-file-map
  ("d" hrm-notes-dired)
  ("g" hrm-notes-grep)
  ("m" hrm-notes-marked))

(add-to-list 'embark-keymap-alist `(,hrm-notes-category . hrm-notes-map))
;; make embark-export use dired for notes
(setf (alist-get hrm-notes-category embark-exporters-alist) #'embark-export-dired)
Clone this wiki locally