Skip to content

Instantly share code, notes, and snippets.

@redblobgames
Last active January 31, 2022 23:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save redblobgames/3ef970bdeeef0e4a025d2981ce83ed27 to your computer and use it in GitHub Desktop.
Save redblobgames/3ef970bdeeef0e4a025d2981ce83ed27 to your computer and use it in GitHub Desktop.
My note taking setup in emacs
;;; my-notes.el ---
;;; Commentary:
;; My notes, inspired by org-roam, but built on other packages I already use.
;;
;; 1. Capture into a queue (org-capture)
;; 2. Review queue (org-agenda)
;; 3. Find note (find-file in folder).
;; 4. Make new note. Fill with title, date.
;; 5. Link to existing note. C-u C-c C-l.
;; Once most of my notes have titles, it would be nice to have 3 and 5
;; use the note titles. It'd also be nice to be able to rename a note
;; title and have the filename change to match, updating links to the
;; file from both my notes folder and my journal folder.
;;; Code:
;; NOTE: I use ivy, counsel for some of thse but vertico, selectrum, consult probably would work too
(require 'hydra)
(require 'org)
(require 'org-drill)
(defvar my/notes-directory "~/Documents/notes")
(defhydra hydra-notes (:color blue)
("n" my/notes-new "new")
("o" my/notes-open "open")
("s" my/notes-search "search")
("S" my/notes-search-content "search contents")
("d" my/notes-directory "directory")
("c" (lambda () (interactive) (org-capture nil "t")) "capture")
("a" org-agenda "agenda review")
("D" my/org-drill "drill"))
(bind-key "H-n" 'hydra-notes/body)
(defun my/notes-new (title)
"Create a new note given a title"
(interactive "sTitle: ")
(let ((default-directory (concat my/notes-directory "/")))
(find-file (concat (my/title-to-filename title) ".org"))
(when (= 0 (buffer-size))
(insert "#+title: " title "\n"
"#+date: ")
(org-insert-time-stamp nil)
(insert "\n\n"))))
(defun my/title-to-filename (title)
"Convert TITLE to a reasonable filename."
;; Based on the slug logic in org-roam, but org-roam also uses a
;; timestamp, and I use only the slug. BTW "slug" comes from
;; <https://en.wikipedia.org/wiki/Clean_URL#Slug>
(setq title (s-downcase title))
(setq title (s-replace-regexp "[^a-zA-Z0-9]+" "-" title))
(setq title (s-replace-regexp "-+" "-" title))
(setq title (s-replace-regexp "^-" "" title))
(setq title (s-replace-regexp "-$" "" title))
title)
(defun my/notes-open (filename)
"Open an existing note."
(interactive)
(let ((default-directory (concat my/notes-directory "/" filename ".org")))
(call-interactively 'find-file)))
(defun my/notes-directory ()
"Open dired with my notes files"
(interactive)
(dired my/notes-directory "-lt"))
(defun my/notes-search ()
"Search my notes for words or phrases"
(interactive)
(counsel-rg nil my/notes-directory "--glob !archive/*"))
(defvar my/notes-search-history)
(defun my/notes-search-content ()
"Full-text search using mdfind. Doesn't show the matched text."
(interactive)
(ivy-read "Search: " #'my/notes-search-function
:dynamic-collection t
:action #'find-file
:history 'my/notes-search-history
:caller 'my/notes))
(defun my/notes-search-function (input)
(or (ivy-more-chars)
(progn
(counsel--async-command (concat "mdfind -onlyin " my/notes-directory "/ "
(shell-quote-argument input)))
'("" "searching..."))))
(defun my/org-drill ()
"Open my drill file and run org-drill"
(interactive)
(find-file (concat org-directory "/drill.org"))
(org-drill))
(provide 'my-notes)
;;; my-notes.el ends here
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment