Skip to content

Find empty notes and put them in a Dired buffer

[ This feature is based on the command denote-sort-dired (Sort files by component). ]

Users may have a workflow where they use the commands denote-link-or-create or denote-link-after-creating (and related) to produce new notes that they plan to elaborate on later (Link to an existing note or create a new one).

To help users find those empty notes, we document the following commands:

  • my-denote-sort-dired-empty-files
  • my-denote-sort-dired-without-empty-files
  • my-denote-sort-dired-all-empty-files
  • my-denote-sort-dired-without-all-empty-files
emacs-lisp
(defun my-denote--note-has-no-contents-p (file)
  "Return non-nil if FILE is an empty note.
This means that FILE conforms with `denote-file-is-note-p' and either
has no contents or has only the front matter."
  (and (denote-file-is-note-p file)
       (or (denote--file-with-temp-buffer file
             (re-search-forward "^$" nil t)
             (if (re-search-forward "[^\s\t\n\r]+" nil t)
                 nil
               t))
           ;; This must come later because here we consider a file
           ;; "empty" even if it only has front matter.
           (denote--file-empty-p file))))

(defun my-denote-sort-dired-empty-files (files-matching-regexp sort-by-component reverse exclude-regexp)
  "Like `denote-sort-dired' but only cover empty files.
Empty files are those that satisfy `my-denote--note-has-no-contents-p'."
  (interactive (append (list (denote-files-matching-regexp-prompt)) (denote-sort-dired--prompts)))
  (pcase-let* ((`(,component . ,reverse-sort) (denote-sort-dired--get-sort-parameters sort-by-component reverse))
               (relative-p (denote-has-single-denote-directory-p))
               (files-fn `(lambda ()
                           (let* ((files (denote-sort-get-directory-files ,files-matching-regexp ',component ,reverse-sort))
                                  (empty-files (seq-filter #'my-denote--note-has-no-contents-p files)))
                             (if relative-p
                                 (mapcar #'file-relative-name empty-files)
                               empty-files)))))
    (if-let* ((directory (if relative-p ; see comment in `denote-file-prompt'
                             (car (denote-directories))
                           (denote-directories-get-common-root)))
              (files (funcall files-fn))
              (buffer-name (funcall denote-sort-dired-buffer-name-function (format "%s (EMPTY)" files-matching-regexp) sort-by-component reverse-sort exclude-regexp)))
        (denote-sort-dired--prepare-buffer directory files-fn buffer-name)
      (message "No matching files for: %s" files-matching-regexp))))

(defun my-denote-sort-dired-without-empty-files (files-matching-regexp sort-by-component reverse exclude-regexp)
  "Like `denote-sort-dired' but omit empty files.
Empty files are those that satisfy `my-denote--note-has-no-contents-p'."
  (interactive (append (list (denote-files-matching-regexp-prompt)) (denote-sort-dired--prompts)))
  (pcase-let* ((`(,component . ,reverse-sort) (denote-sort-dired--get-sort-parameters sort-by-component reverse))
               (relative-p (denote-has-single-denote-directory-p))
               (files-fn `(lambda ()
                           (let* ((files (denote-sort-get-directory-files ,files-matching-regexp ',component ,reverse-sort))
                                  (empty-files (seq-remove #'my-denote--note-has-no-contents-p files)))
                             (if relative-p
                                 (mapcar #'file-relative-name empty-files)
                               empty-files)))))
    (if-let* ((directory (if relative-p ; see comment in `denote-file-prompt'
                             (car (denote-directories))
                           (denote-directories-get-common-root)))
              (files (funcall files-fn))
              (buffer-name (funcall denote-sort-dired-buffer-name-function (format "%s (OMIT EMPTY)" files-matching-regexp) sort-by-component reverse-sort exclude-regexp)))
        (denote-sort-dired--prepare-buffer directory files-fn buffer-name)
      (message "No matching files for: %s" files-matching-regexp))))

(defun my-denote-sort-dired-all-empty-files ()
  "List all empty files in a Dired buffer.
This is the same as calling `my-denote-sort-dired' with a
FILES-MATCHING-REGEXP of \".*\"."
  (declare (interactive-only t))
  (interactive)
  (pcase-let ((`(,sort-by ,reverse ,exclude-rx) (denote-sort-dired--prompts)))
    (my-denote-sort-dired-empty-files ".*" sort-by reverse exclude-rx)))

(defun my-denote-sort-dired-without-all-empty-files ()
  "List all empty files in a Dired buffer.
This is the same as calling `my-denote-sort-dired' with a
FILES-MATCHING-REGEXP of \".*\"."
  (declare (interactive-only t))
  (interactive)
  (pcase-let ((`(,sort-by ,reverse ,exclude-rx) (denote-sort-dired--prompts)))
    (my-denote-sort-dired-without-empty-files ".*" sort-by reverse exclude-rx)))

[ In the above snippet, I am purposefully duplicating code to make it easier for users to pick the ones they need. ]