Configuring bug-reference mode from within org-mode

A few weeks ago I learnt about bug-reference-mode, a neat little Emacs minor-mode that automatically links text to a bug tracker. A few of my projects use bug trackers or GitHub issues, and different clients often have their own systems.

There are two variables that I usually want to set for bug-reference-mode:

bug-reference-url-format
The URL of an individual bug, with %s used as a placeholder for the bug ID.
bug-reference-bug-regexp
The regular expression that finds bug ID references. This is usually something like #1234.

I use directory local variables1 for a lot of projects, but Emacs also supports file local variables for more fine-grained settings. org-mode has its own syntax for setting file local variables, so I needed to adjust things slightly to get it all working.

Setting local variables for a bug tracker looks like this:

# Local Variables:
# mode: org
# bug-reference-url-format: "https://example.org/tasks/%s"
# bug-reference-bug-regexp: "\\(#\\)\\([0-9]\\{8\\}\\)"
# End:

On my first attempt I skipped the Local Variables: and End: keywords, but they are needed for org to pick these variables up.

Footnotes:

1

These are variables that are set for all files in a directory and its sub-directories.


Extracting zetteldeft notes

I've been using zetteldeft to manage my notes for about six months now. Recently I've been wanting to reorganize my notes as some of them don't follow the zettelkasten principal of one idea per note.

Okay, most of them don't follow that principal if I'm honest about it.

I wanted to slice up my existing notes into smaller files and then link them all together. In my mind the process would go something like:

  1. Highlight the region to extract.
  2. Enter a title for the new note.
  3. Emacs would automatically move the text to the new note and insert a link where it used to be.

Emacs macros are handy for this kind of repeatable behaviour, so I started recording with an example note.

However, I quickly ran into a roadblock due to zetteldeft's behaviour when creating a new note. zetteldeft will create a file, insert the title, and save the file name to the Emacs kill ring. This meant any text I'd copied would be overwritten by the note title.

I decided to abandon the macro method and use a dedicated function instead. I wrote a little function called sodaware/zd-extract-region-to-note that extracts a highlighted region to a new note:

(defun sodaware/zd-extract-region-to-note (title)
  "Extract the marked region to a new note with TITLE."
  (interactive (list (read-string "Note title: ")))
  (let* ((deft-use-filename-as-title t)
	 (note-id (zetteldeft-generate-id title))
	 (note-filename (concat note-id zetteldeft-id-filename-separator title))
	 (note-text     (kill-region (region-beginning) (region-end))))
    (save-excursion
      (deft-new-file-named note-filename)
      (zetteldeft--insert-title title)
      (insert "\n\n")
      (yank)
      (save-buffer)
      (when (featurep 'evil) (evil-insert-state)))
    (sodaware/zd-insert-link note-filename title)))

(defun sodaware/zd-insert-link (note title)
  "Insert a link to NOTE with TITLE as its link text."
  (interactive)
  (insert (format "[[zdlink:%s][%s]]" note title)))

I use the zdlink protocol for my links, but the sodaware/zd-insert-link function can be modified to use any kind of link. Eventually I'd like this to be controlled via a variable.

I'd also like a function that returns focus to the original note after extraction, but for now I'm pretty happy with how things work.


Displaying my monthly goals in org-agenda

Each month I set primary and secondary goals as part of my Groundhog Day Resolutions process. Setting goals is good, but I have a nasty habit of forgetting about them until a few days before they're due. Not good.

To combat this issue I wanted to add my goals somewhere I would see them every day: my org agenda.

My daily agenda display currently looks like this (minus client/private tasks):

My initial org-agenda

I wanted to keep the same agenda information, but with my monthly goals at the top so that I can't ignore them.

Thankfully org-agenda can be configured to show different views and agendas via the org-agenda-custom-commands variable.

The first step was to create a place to store my goals. I created an org file with TODO entries for each goal, all stored under a headline for the month. These entries are tagged with :GHD: so they can be filtered, and I added an :ACTIVE: tag for the current month.

The finished goals.org is laid out like this:

* TODO March 2021 [0/7] :GHD:ACTIVE:
** TODO [#A] Finish version 1.0 of Writing PHP with Emacs
** TODO [#A] Complete another session of deliberate practice
** TODO [#A] Finish version 0.3 of Craft Roulette
** TODO [#A] Contribute to a free software project
** TODO [#C] Run 120 miles
** TODO [#C] Read a book
** TODO [#C] Add detail pages to my secondary goals

The tag properties are inherited from the parent for each child goal, which makes setting the active month much simpler.

My first attempt used A and B for the priorities, but org-agenda gives all tasks a priority of B by default so the month headline was showing in the final view. Using A and C for my primary and secondary goals fixes this.

The custom agenda is made up of three parts:

  1. A list of primary goals.
  2. A list of secondary goals.
  3. The regular org agenda which shows scheduled tasks and deadlines.

I used the tags-todo command to list goals; it can filter tasks by file, tag, and priority which makes it perfect for the job. The configuration to show my primary goals is fairly simple:

(tags-todo "GHD+ACTIVE+PRIORITY=\"A\""
	   ((org-agenda-files '("~/org/goals.org"))))

This displays all TODO items in my "~/org/goals.org" file that have :GHD: and :ACTIVE: tags AND a priority of A. To display secondary goals, all I need to do is replace the priority with C.

After the tags-todo command I use the agenda command with the timespan set to one day/one week depending on the view.

The finished configuration looks like this (with a little modification for brevity/online readability):

(setq org-agenda-custom-commands
'(("d" "Today's Tasks"
	((tags-todo
	  "GHD+ACTIVE+PRIORITY=\"A\""
	  ((org-agenda-files '("~/org/goals.org"))
	   (org-agenda-overriding-header "Primary goals this month")))
	 (tags-todo
	  "GHD+ACTIVE+PRIORITY=\"C\""
	  ((org-agenda-files '("~/org/goals.org"))
	   (org-agenda-overriding-header "Secondary goals this month")))
	 (agenda "" ((org-agenda-span 1)
		     (org-agenda-overriding-header "Today")))))

  ("w" "This Week's Tasks"
       ((tags-todo
	 "GHD+ACTIVE+PRIORITY=\"A\""
	 ((org-agenda-files '("~/org/goals.org"))
	  (org-agenda-overriding-header "Primary goals this month")))
	(tags-todo
	 "GHD+ACTIVE+PRIORITY=\"C\""
	 ((org-agenda-files '("~/org/goals.org"))
	  (org-agenda-overriding-header "Secondary goals this month")))
	(agenda))))
)

I can now view a daily agenda using C-c a d, or a weekly one using C-c a w, and the agenda has both sets of goals at the top:

The new and improved org-agenda

Now I have no excuse for forgetting them.


Adding emoji to Emacs buffers with emojify

emojify is an Emacs package that adds emoji support to any Emacs buffer.

emojify-mode works in all kinds of places

Running M-x emojify-mode will turn on emoji support for the current buffer. It does not alter the contents of the buffer, only how it's displayed. This makes it safer to use, but it does have some weird side-effects when aligning text in certain buffers (like org-agenda).

There is also no support for html export, although there's a GitHub issue open with some workarounds.

Any required images will be downloaded the first time running emojify-mode, and a full list of supported emojis can be viewed with emojify-list-emojis. There are over 5,000 so it can take a while to display.

global-emojify-mode can be used enable the mode everywhere:

;; Enable everywhere via an init hook.
(add-hook 'after-init-hook #'global-emojify-mode)

;; Or with use-package.
(use-package emojify
  :init
  (global-emojify-mode))

I prefer to enable emojify on a per-project basis, so I use .dir-locals.el to enable it for org and markdown buffers:

((org-mode      . ((mode . emojify)))
 (markdown-mode . ((mode . emojify))))

Yet another reason to ❤️ Emacs.


Finding areas to improve

One of my major goals for this year is to improve my processes. It's a bit of a vague goal at the moment, but the general idea is to examine how I currently get things done and to tweak things as needed.

I've been doing the same job for over a decade now (and programming computers even longer) so I feel like there there are plenty of areas where I've stagnated.

The first step is finding where I can make improvements. There are a couple of different ways I can do this:

  1. Thinking through things – This is a bit like proofreading my own work; it finds problems, but it misses just as many. I still think it's useful to sit down and examine my routines, but it's not a foolproof strategy.
  2. Get outside help – If the previous method is proofreading, this is hiring an editor. I get regular feedback on my work from clients, but it's usually on what I've done, not how I've done it.
  3. Record myself working – It's pretty common for people to record themselves to check their form when working out. This is the same thing, but with boring work instead of lifting weights.

Right now I'm trying out #3 with some success. It's a little time consuming to watch videos of my work, but it's helped me find a couple of small things already:

  • When working BlitzMax code I spend a fair amount of time typing closing statements like End Function or End Method. This is something I can fix in blitzmax-mode.
  • I'm not comfortable renaming files I'm working on from within Emacs, so I'll open the file manager to do it.
  • I'll switch to a browser to look something up and usually end up getting distracted. Either blocking distracting sites or using Emacs to search documentation would fix this.

There are still plenty of things I'd like to improve on, but these came up within a few sessions of recording myself.