All my notes live in Emacs' org-mode. Lot's of my notes are about GitHub. I
found many instances of https://github.com/pulumi/registry/issues/12345
in my notes1,
mostly hand typed. This was long2, both to type and to more importantly to read. Link
descriptions are the preferred way to deal with this in org-mode, but they require
even more typing. They look like this:
[[https://example.com][EXAMPLE]
And render like this:
That's a lot of typing. We can do better. GitHub issues are often abbreviated as
<org>/<repo>#<issue-number>
, and GitHub is often abbreviated as GH
. I want to be able
to type out GitHub issue links like this:
gh:pulumi/registry#12345
In the rest of this post, I'll explain the full scope of what we are trying to do, and how do accomplish that goal in only 65 lines of Emacs Lisp.
The task
We should introduce a new link type3 (gh
) that is easy to write. Links written with
our new link type should open correctly (C-o
), export correctly and generally work as
expected. This is the minimum requirement for this to be useful.
We also have a bonus task: Links should be able to generate their own description. That
means going from gh:rust-lang/rust#31844
to Tracking issue for specialization (RFC 1210) #31844
when desired. This isn't required, but it makes our gh
link type more
useful.
Implementation
The first thing we need to do is define a new link type. On modern Emacs, we do that with
org-link-set-parameters
4:
This tells org-mode that gh
is a valid link type, and that org-gh--follow
should be
invoked when you try to open it. The code for org-gh--follow
is straight-forward:
It relies on some helper functions that we will use throughout this code:
We now have working link highlighting and following:
This works up until you try to export your gh
link to another format. It doesn't export
well:
Language | Output |
---|---|
HTML | <a href="gh:a/b#1">gh:a/b#1</a> |
Markdown | <gh:a/b#1> |
We need to tell org-mode how to export our link type:
Let me explain how this works.5 When org-mode exports anything, it exports to a
backend. Each backend defines a series of transcoders, which org-mode calls as appropriate
on the parsed tree it is exporting. In org-gh--export
, we get the transocder used for
links, and then fabricate a synthetic HTTPS style link to invoke the transocder on. This
allows us to avoid needing to define a separate behavior for each backend type. We don't
want to need to update our gh
link type for each backend that org-mode could export to.
These 27 lines are enough to accomplish the basic task we set out to complete.
- Typing
gh:org/repo#issue
highlights as a link. - Calling
Ctrl-O
(org-open-at-point
) takes us to the issues page. - Exporting custom links correctly exports
https
links.
I believe that this is enough for a useful extension to org-mode. It adds a new link type,
inheriting most behavior correctly from the https:
link type.
Bonus - Self-generating descriptions
Now that we have our own link type, we can do cool things with it. Let's make gh
links
automatically default their description to the issue or pull request's title.
We can do this through the :insert-description
link parameter:
This allows us to give a default description for gh
type links. For a default
description, we use <org>/<repo>#<number>: <title>
. It's a bit verbose, but very
explicit.
The wrapping (or description
means that if there is already a description for the link,
we leave that as is. Then we simply fetch the title and past it
in. org-gh--get-issue-title
just invokes the gh CLI tool to get the title of a
(repository,issue number) pair as our default description:
That's all you need to get default title descriptions.
Conclusion
I hope you have learned how to add custom link types to org-mode through this example. I hope this gives you some confidence in customizing Emacs yourself. You can find the complete code from this article on GitHub.
I work at Pulumi. Pulumi repos come up a lot.
Comparitivly, this whole post is about a workflow micro-optimization.
In org-mode, link types are defined by their prefix, seperated by a :
.
org-link-abbrev-alist
doesn't work for raw links, so we don't want to use it. A
raw link is a link of the form gh:example/repo#1234
, instead of
[[gh:example/repo#1234]]
.
Getting this to work took me way to long.