I noticed a pattern during work video calls: I found myself regularly sharing my screen and opening Notepad to capture notes and for use as a team whiteboard. While useful during the call, the result was a jumble of files with notes.txt somewhere in the name.
It occurred to me that I needed a better system. I wanted an effortless way to capture notes that had archival organization baked in. With a bit of elisp I was able to get emacs to tackle this job.
Let's walk through my solution.
I set up a new keyboard mapping for Control-x t that invokes the function project-notes-find-file-today. This function visits a file that's relevant to what I'm currently working on within emacs. Here's the code to set up the keyboard shortcut:
(defun my-minimal-dev-keys-hook ()
(local-set-key (kbd "C-x t p") 'browse-project-resource-url)
(local-set-key (kbd "C-x t n") 'project-notes-find-file-today)
(local-set-key (kbd "C-x t s") 'ag-project-regexp)
(local-set-key (kbd "C-x t D") 'define-word-at-point))
Here's the definition of project-notes-find-file-today. It starts by figuring out a few key facts:
- What's the base note taking directory?
- What's today's date in YYYY-MM-DDD format?
- What's the name of the source code branch that I'm currently looking at?
- What's the name of the customer associated with these files?
Once I know this information, it's straightforward to call find-file on a path that looks like so:
[Notes Directory]/[Customer]/[Branch]/[Today's Date].md
This arrangements organizes my notes by customer, project and date and seems like a reasonable balance between creating too many and too few files.
(defun project-notes-find-file-today ()
(interactive)
"Quickly open up a notes file or create one for today."
(let* ((base "~/dt/i2x/project-notes/src/main")
(timestamp (format-time-string "%Y-%m-%d"))
(branch (current-vc-branch))
(customer (current-customer))
(dir (cond
((and branch customer)
(format "%s/%s/%s" base customer branch))
(customer
(format "%s/%s/misc" base customer))
(t (format "%s/internal/misc" base)))))
(unless (file-accessible-directory-p dir)
(mkdir dir t))
(find-file (format "%s/%s.md" dir timestamp))))
All that remains is to define current-customer and current-vc-branch. current-customer is pretty straightforward because I use the following convention:
dt/i2x/[Customer Name]/src/...
If I can find dt/i2x then I know the next directory name is the customer.
(defun current-customer ()
"Guess the customer we are looking at by looking at our path"
(let ((d default-directory))
(if (string-match "dt/i2x/\\(.*?\\)/" d)
(match-string 1 d)
nil)))
current-vc-branch is a tiny bit more sophisticated. For one thing, some of my projects use Git while others use Subversion. I was surprised that there wasn't an existing elisp function to tell me which version control system managed an existing directory. Fortunately, both Git and Subversion use a well know directory I can search for to figure out which tool is active.
From there, I was able to use the built in vc-git function vc-git--symbolic-ref to find out the current git branch name. I derive the Subversion branch name by using dsvn's svn-current-url function.
(defun current-vc-branch ()
"Guess the current branch or return nil if aren't on one"
(require 'dsvn)
(let ((d default-directory))
(cond
((locate-dominating-file d ".git")
(vc-git--symbolic-ref default-directory))
((locate-dominating-file d ".svn")
(let ((url (svn-current-url)))
(cond ((string-match "/trunk\\(/\\|$\\)" url) "trunk")
((string-match "/branches/\\(.*?\\)\\(/\\|$\\)" url)
(match-string 1 url))
(t nil))))
(t nil))))
When I run Control-x t a file is ultimately visited under dt/i2x/project-notes/main. I've arranged for that directory to be a Git repository. After my meeting or other session that inspired me to capture notes, I commit any new or modified files and do a push a remote repository. The result is that my project notes are saved in the cloud and accessible from all the devices I develop on.
And just like that, tracking on-demand project notes went from a chore to a joy.
No comments:
Post a Comment