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