Skip to content

Instantly share code, notes, and snippets.

@amitp
Created December 1, 2019 18:19
Show Gist options
  • Save amitp/7a133026dafac2131e5f023d43acbc16 to your computer and use it in GitHub Desktop.
Save amitp/7a133026dafac2131e5f023d43acbc16 to your computer and use it in GitHub Desktop.
My org-mode journal
;;; my-diary.el ---
;;; Commentary:
;; My daily diary, inspired by org-journal, but customized for my needs.
;; Timestamps rounded to the nearest 5 minutes.
;;
;; My keybindings: H-j to open the diary file (global binding), then
;; H-j to add an entry (local binding). C-c C-b, C-c C-f to move days
;; (like org-journal). H-i to search previous headings for completion,
;; so that I can reuse headings and then analyze them.
;;; Code:
(require 'org)
;;; Configuration variables
(defvar my-diary-directory "~/Documents/journal/")
(defvar my-diary-filename (expand-file-name (concat my-diary-directory "%Y-%m-%d.org")))
(defvar my-diary-filename-regexp
(concat "\\`"
(s-replace "%Y-%m-%d" "\\(....-..-..\\)"
(regexp-quote my-diary-filename))
"\\'"))
(add-to-list 'auto-mode-alist (cons my-diary-filename-regexp 'my-diary-mode))
;;; Helper functions
(defun my-diary-filename-to-date (filename)
(let ((str (cadr (s-match my-diary-filename-regexp (expand-file-name filename)))))
(when str
(let* ((time (parse-time-string str))
(date (list (nth 4 time) (nth 3 time) (nth 5 time))))
date))))
(defun my-diary-calendar-day-to-time (calendar-day)
"Emacs day/time functions are … lacking"
(encode-time 0 0 0 (nth 1 calendar-day) (nth 0 calendar-day) (nth 2 calendar-day)))
(defun my-diary-change-day (dir)
"Construct filename 'dir' days from current filename"
(let* ((day (calendar-absolute-from-gregorian (my-diary-filename-to-date (buffer-file-name))))
(newday (calendar-gregorian-from-absolute (+ day dir))))
(format-time-string
my-diary-filename
(my-diary-calendar-day-to-time newday))))
;;; Commands
(defun my-diary-today ()
"Open today's diary file"
(interactive)
(find-file (format-time-string my-diary-filename))
(when (equal (point-max) 1)
(let ((calendar-date (my-diary-filename-to-date (buffer-file-name))))
(insert (format-time-string
(read-file-into-string
(concat my-diary-directory "/diary-template.org"))
(my-diary-calendar-day-to-time calendar-date)))))
(goto-char (point-max)))
(defun my-diary-new-entry ()
"Make a new entry, rounded to the nearest 5 minutes"
(interactive)
(my-diary-today)
(insert (format-time-string "** %H:%M " (org-current-time 5))))
(defun my-diary-search-headings ()
"Search through previous headings for completion"
(interactive)
(let ((headings (split-string
(shell-command-to-string
(concat "rg --no-filename '^[*][*] \\d\\d:\\d\\d (.*)$' --replace '$1' " my-diary-directory " | sort | uniq -c | sort -n -r | perl -pe 's/^ *\\d* *//'"))
"\n")))
(ivy-read "Headings: "
headings
:caller 'my-diary-heading
:action #'insert)))
(defun my-diary-next-day ()
"Move to the next day's diary file"
(interactive)
(find-file (my-diary-change-day 1)))
(defun my-diary-prev-day ()
"Move to the previous day's diary file"
(interactive)
(find-file (my-diary-change-day -1)))
(define-derived-mode my-diary-mode org-mode "Journal"
"Mode for writing diary entries"
(face-remap-add-relative 'default :family "Work Sans" :height 1.1)
(turn-on-visual-line-mode)
(run-mode-hooks))
(provide 'my-diary)
;;; my-diary.el ends here
@amitp
Copy link
Author

amitp commented Sep 5, 2020

I used org-capture for several years and then org-journal for five years. I switched to my own code because org-journal was getting slower over time (I have 2000+ journal files and my laptop is 7+ years old), and also because org-journal didn't let me customize some things.

The main thing I needed was to open a file, move the cursor to the end, and insert a headline. This is similar to org-capture. Other features I wanted:

  1. Template: I want to start each new day with a health template that reminds me to log my sleep, caffeine intake, exercise, etc.
  2. Fast search: ripgrep over my 2000+ daily journal files takes 0.2 seconds, which is much faster than org-journal's search.
  3. Headline completion: I use ripgrep here too to complete over previously entered headlines. For example if I'm playing Factorio, I already have a previous headline Factorio :game:, so I can type f a c and complete the rest of this.
  4. Time analysis: I have python programs that parse my journals and give me information about my work, sleep, health, etc. The health data template and the reuse of headlines make the data more consistent, which makes the analysis easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment