Written on 7th March 2025
Status:There are a million pages on the internet telling you how to take notes. I'm not really sure the world needs another, so I'm writing this for my own reference.
I've recently been experimenting with using the emacs software to take notes. I have a love-hate relationship with it:
- Negatives: I hate how it is weird, arcane and looks like it has come straight out of the 1980s.
- Positives: I love how it is weird, arcane and looks like it has come straight out of the 1980s.
Given enough knowledge and learning it apparently possible to configure emacs how you want. Long-term users describe it using terms like "A well worn pair of comfortable shoes" so I was curious to try. I'm documenting the experiments here and maybe sometime I can expand this post to make it more like a tutorial.
I'm still haven't completedly made up my mind about emacs, but it's been OK for note-taking just now.
I've made the following customisations:
- I've added a top-level "Colin" menu. This has two entries - one to open my notes file, and another to add a new note.
- I organise the notes by linking between them. I can 'right-click' on a note and copy the note's ID to the 'clipboard' and paste it into another location.
- I can 'right-click' on a note and search to see which other notes link to it.
The whole thing looks like this:
Step1: Cleaning up the screen and various settings
I found the initial emacs display quite cluttered. Using proportional fonts and hiding some of the markup made things better.
I modified the theme described at: Beautifying org mode in emacs
I changed various settings:
;; Allow Alt-Enter in org mode to be used as a workaround when adding list items - this prevents a blank line being added ;; https://emacs.stackexchange.com/questions/51117/org-mode-control-whether-m-ret-inserts-a-newline-or-not-for-plain-lists#51119 (setq org-blank-before-new-entry (quote ((heading . nil) (plain-list-item . nil)))) ;; Allow shift up and down to work in org mode (setq org-replace-disputed-keys t) ;; But this seems to be the line needed to select headlines? (setq org-support-shift-select 't) ;; Make text wrap in org mode (add-hook 'org-mode-hook 'visual-line-mode) ;; Make things indent as groups in org mode ;(add-hook 'org-mode-hook 'org-indent-mode) (setq org-startup-indented 't)
Step2: Adding a new note
I've been keeping notes in a single text file for ages, using a variety of different editors.
My process works like this:
- I write new notes by adding them to the end of a text file file. This solves any problem of "where to put them". I also find storing notes in this chronological order is useful - I can see what else I was doing/writing about at a time. It also narrows the gap between a "blog" and a "journal".
- I begin a note's title with the date and time. This provides an identifier I can use to reference the note.
The first thing I did was to create an emacs function to open my notes, and add a new note. This opens the notes file, moves to the end and inserts the current date and time:
(defun colin-new-note () "Colin's function to open the notes file and add a new one." (interactive) (find-file "~/Documents/misc_stuff/my documents and notes/colin.org") (goto-char (point-max)) (insert (format-time-string "\n* %Y-%m-%d-%H%M%S " (current-time))) )
…and I then told emacs whenever I pressed a key sequence to run this function:
(global-set-key "\C-ccn" 'colin-new-note)
When I press the (rather arcane) sequence of keys: 'Control' and 'c', then 'c', then 'n' ("Colin, Create Note") it lets me add a new note I've made emacs slightly my own!
(Emacs experts could argue I should use 'org-capture' to do this. I wrote this before I was using org-mode, and I still think it's useful to have simple, "tool agnostic" functions)
Step3: Adding images
Next, I wanted to add images to my notes so I made a helper function to let me choose an image and insert a link.
This:
- Copies the image from anywhere on my computer into an "images" directory next to my notes.
- Resizes the image.
- Adds a link to the image into the note.
It then uses imagemagick (which needs to be installed).
(setq local-images-dir "images") (setq local-image-size-pixels 800) (defun colin-insert-image (source-name) "Prompt for an image filename, copy and reisze it and insert a link into the current point" (interactive "f") (unless (file-exists-p local-images-dir) (make-directory local-images-dir)) (let* ((image-name (file-name-nondirectory source-name)) (destination-name (format "%s/%s" local-images-dir image-name)) ) ;; Now copy and resize the image, stripping out EXIF info (shell-command (format "magick '%s' -strip -auto-orient -resize %d '%s'" source-name local-image-size-pixels destination-name)) (insert (format "[[./%s]]" destination-name)) ) ;; Make org display the image (org-redisplay-inline-images) )
Step4: Linking notes together
This creates hyperlinks using org-mode. Links are made by:
- Going to one note and running 'colin-set-or-copy-id' - this copies a link.
- Going to the other note and pasting in the link.
(You could argue that I should use 'org-store-link' and 'org-insert-link' instead. I prefer my version for these reasons:
- It uses my headline format as an ID (I was using this scheme before I started using org-mode so already have lots of notes written).
- It doesn't need a special insert function - you can "paste" a link like anything else.
- The link just contains the ID instead of a full text. I find this is good as I like to write context around the link - but perhaps I'll revert to having a whole text in the link. The(my) jury is out on this one.
These functions:
- Look to see if the current note has an org mode ID set.
- If not, extracts the time and date from the current note, and uses it as an ID.
- Copies a hyperlink to the "clipboard" (emacs calls this the kill-ring but I really don't like the sound of this term).
;; Extracted this from the guts of an org-mode function - there doesn't seeem ;; to be a clean built-in way in org-mode (defun custom-set-org-id-at-point(id) "This is a modified version of org-id-get which sets a specific ID rather than generates one" (org-set-property "ID" id) (org-id-add-location id (or org-id-overriding-file-name (buffer-file-name (buffer-base-buffer)))) ) (defun colin-get-or-set-id-from-headline () "Try to find the current org ID. If it doesn't exist prompt for one, proposing the first word of the current headline" (let* ( (existing-id (org-entry-get nil "ID")) ) ;; If the ID exists, use it... (if existing-id existing-id ;; Otherwise ask for it, prompting with our proposal. (let* ( ;; Find the headline... (headline (org-entry-get nil "ITEM")) ;; And extract the first word - Normally I use a timestamp in the headline (proposed-identifier (car (split-string headline " "))) ;; Ask for it... (entered-identifier (read-from-minibuffer "ID:" proposed-identifier)) ) ;; And place it into the org document (custom-set-org-id-at-point entered-identifier) ;; Return it from the let binding to be set as identifier-to-use entered-identifier ) ) ) ) (defun colin-set-and-copy-id () "Find an ID in the current note. Prompt for it if it's not there. Copy the ID to the "kill-ring" in a format where it can be pasted as an org link" (interactive) ;; Find the ID of the current headline, or add it if it isn't there. (let ((identifier-to-use (colin-get-or-set-id-from-headline))) ;; Now we have the identifier... (message (format "Copied link to: %s" identifier-to-use)) ;; ... copy it in orgmode hyperlink format to the "clipboard" (kill-new (format "[[id:%s]]" identifier-to-use)) ) )
Step5: Finding backlinks to the current note:
I thought it could be useful to see which notes refer to the current note. There are various tools and schemes to do this (e.g. org-roam and others. Rather than use these I search for the ID of the current note - this seems to be much simpler than having databases and so on, and hopefully will be adequate:
;; Find backlinks to the current org node (defun colin-find-backlinks () "Use org search to find backlinks for the current node. Note this only works at the top level heading; it fails if within a subheading" (interactive) (org-search-view nil (format "id:%s" (org-id-get)) nil) )
This currently isn't perfect: It uses (org-id-get) to get the current ID, and then org-search-view to find it. Unfortunately it doesn't currently work well with subheadings as these have no ID - I need to find some way to get org-id-get move up to the parent to get its ID if the child doesn't have one.
Step6: Adding menus and keyboard shortcuts:
Define a "Colin" menu which is accessible from the menu bar:
;; Menu stuff ;; ;; https://emacs.stackexchange.com/questions/15093/how-to-add-an-item-to-the-menu-bar (defvar colin-menu-bar-menu (make-sparse-keymap "Colin")) (define-key global-map [menu-bar my-menu] (cons "Colin" colin-menu-bar-menu)) (define-key colin-menu-bar-menu [colin-new-note] '(menu-item "Add a new note" colin-new-note :help "Add a new note")) (define-key colin-menu-bar-menu [colin-load-note] '(menu-item "Open notes" (lambda () (interactive) (find-file "~/Documents/misc_stuff/my documents and notes/colin.org") (goto-char (point-max)) ) :help "Open my notes file"))
Make right-clicking on a note open a context menu. I learned this from https://oylenshpeegul.gitlab.io/blog/posts/20230129/
(context-menu-mode) (defun colin-context-menu (menu click) "Colin's stuff" (define-key-after menu [find-backlinks] '(menu-item "Colin: Find backlinks to the current node" colin-find-backlinks :help "Find backlinks using org search")) (define-key-after menu [copy-link] '(menu-item "Colin: Copy link to the current node" colin-set-and-copy-id :help "Set the org ID and copy it to the clipboard")) menu) ;; hook into context menu (add-hook 'context-menu-functions #'colin-context-menu)
Step7: Searching
I decided to make a quick org search shortcut, which I've bound to Ctrl-/ - Again, maybe there are better ways of doing it.
This searches all agenda files for the string, and displays the titles of the notes that match.
(defun colin-agenda-search (text) (interactive "sSearch for:") (org-search-view nil text nil) ) (global-set-key (kbd "C-/") 'colin-agenda-search)
Step8: Enhancement: Putting a link into a page to list tagged items, or a text search:
I wanted to be able to have a page that allows me to search for tagged items. To do this I added custom org link handlers:
(defun _colin-org-search-link-open (text _) "Internal function to follow a text link by using an org search" (org-search-view nil (format "+%s" text) nil) ) (defun _colin-org-search-tag-open (tag _) "Internal function to follow a tagged link by using an org search" (org-search-view nil (format "+:%s:" tag) nil) ) ;; Now define my custom org link prefixes (org-link-set-parameters "search" :follow #'_colin-org-search-link-open) (org-link-set-parameters "tagged" :follow #'_colin-org-search-tag-open)
This allows the following links to work, which bring up org agenda displays:
- search - searches all files in the agenda for matching text
tagged - searches all files in the agenda for items with matching tags.
So I can use these in a "start" page to search for things.