Markdown reference links in TextMate

Brett Terpstra has released another clever Markdown tool to sit next to his Marked application and Blogsmith Bundle. It’s a sort of Markdown tidy, a filter that runs through a file and turns all your inline links into reference links.

So there’s no misunderstanding, let’s do a quick vocabulary review. Inline links look like this:

This is the [linked text](http://daringfireball.net/projects/markdown)
we use to jump to Gruber's page.

The URL for the link comes right after the linked text. Reference links look like this:

This is the [linked text][1] we use to jump to Gruber's page.

<blah blah blah>

[1]: http://daringfireball.net/projects/markdown

The linked text is followed by a short bit of text, the reference, in brackets. (I generally use numbers, but you can use any text string as the reference.) Elsewhere in the document is that same reference at the start of a line, followed by a colon and the URL. The reference can be anywhere, but those of us with anal retentive tendencies like to put them in a nice neat set at the end of the document.1

Reference links are Markdown’s greatest contribution to document readability. Compare this,

This is the [linked text][1] we use to jump to Gruber's page.

with this,

This is the <a href="http://daringfireball.net/projects/markdown">linked text</a>
we use to jump to Gruber's page.

In HTML, the text we’re actually supposed to be reading gets lost in between the tags. And it’s even worse when the URL is long.

One problem with reference links is all the jumping around you have to do to put them in your document. Put brackets around the linked text, add the reference, go to the bottom of the document, put in the reference and the URL, then go back to spot you started at. You don’t get lost reading reference links, but you can get lost writing them.

That’s the problem Brett’s command solves. You use the easy-to-write inline links as you’re composing, then convert them all into the easy-to-read reference links when its convenient. Best of both worlds.

Brett wrote it as an OS X Service, but late last week he and John Gruber collaborated via Twitter (you can start with this tweet and work backwards) and got it working as a BBEdit command, too.2

Good as Brett’s command is, I have no use for it. A few years ago I wrote a set of TextMate commands that do all the jumping around needed to insert reference links as I write.

When I wrote them back in 2006, I really had no idea how TextMate bundles worked, so they weren’t done in a “bundle friendly” fashion. I suppose I should rewrite them and turn them into a proper bundle someday.

But however poorly structured they are, they work really well and have saved me much time and frustration in using reference links on this blog.

In a similar vein are David Sparks’ TextExpander snippets for handling reference links. They’re very well done, but because they’re implemented in TextExpander, they can’t tell the text editor to put all the references together at the bottom of the document. You have to do that yourself via cut-and-paste. I don’t have the patience for that, but if you’re the kind of person who moves from one text editor to another (as that tramp David does) the portability of TextExpander snippets is a big advantage.

I would think that making a similar set of commands for BBEdit would be pretty easy—easier than what I did for TextMate, since BBEdit has a nice AppleScript library that allows much more control over cursor movement than TextMate does. (I mention this mainly to see if I can goad Clark Goble into writing them. If I ever have to move to BBEdit, it’d be nice if some commands I use every day were already waiting for me.)

Let’s see, who else in the Mac community can I namecheck in this post? How about John Siracusa? In the “Square Bracket Colon Smiley” episode of Hypercritical, he says that reference links in Markdown can be troublesome because you have to scroll down to the bottom of the document to check on your links. One can imagine that this is an acute problem for someone who writes 80,000 word posts, but it’s an annoyance for the rest of us, too.

When you want to use the same reference link more than once in the same document, you either have to remember what reference you gave it (not easy if you’re using auto-generated references as I am) or scroll down to find the reference and scroll back up to insert it at the second link. Neither of these solutions is appealing.

Brett’s Blogsmith Bundle has a Link to Reference command that gathers all the reference links in your document and presents them to you in a little popup menu. Because I use numbers as my references instead of descriptive strings, the command as written is of little use to me, but the idea of putting all the existing reference links in a menu is brilliant.

Blogsmith popup menu for reference links

I don’t know enough Ruby to alter Brett’s command to fit my needs, so I started from scratch with Python. Also, I’ve never really understood Interface Builder, so I decided to use CocoaDialog instead of the dialog bundle to generate the menu of links. My menu isn’t as slick as Brett’s but it does the job.

Popup menu of reference links

When I want to add a new reference link to a URL I’ve previously linked to, I select the text I want to use as the link and press ⌃⌥R. Up comes a little window with a dropdown menu of all the URLs already included in the document. I choose the appropriate URL from the menu and the selected text gets turned into a reference link to that URL. This solves the Siracusa problem of excessive scrolling.

Here’s how the command is set up in TextMate’s Bundle Editor:

Choose reference link command

Here’s the full source of the command:

python:
 1:  #!/usr/bin/python
 2:  # -*- coding: utf-8 -*-
 3:  
 4:  import sys
 5:  import re
 6:  import os
 7:  from subprocess import *
 8:  
 9:  # Set the environment for calling CocoaDialog.
10:  dialogpath = os.environ['TM_SUPPORT_PATH'] + '/bin/CocoaDialog.app/Contents/MacOS/'
11:  dialogenv = {'PATH': dialogpath}
12:  
13:  # Utility function for shortening text.
14:  def shorten(str, n):
15:    'Truncate str to no more than n chars'
16:    return str if len(str) <= n else str[:n-1] + '…'
17:  
18:  # Get the selection.
19:  try:
20:    selection = os.environ['TM_SELECTED_TEXT']
21:  except KeyError:
22:    selection = ''
23:  
24:  # Read in the buffer and collect all the reference links.
25:  text = sys.stdin.read()
26:  refs = re.findall(r'^\[([^^\]]+)\]: +(https?://(.+))$', text, re.MULTILINE)
27:  
28:  # Run the dialog asking for which link to use.
29:  choices = [ shorten(x[2], 100) for x in refs ]
30:  chosen = Popen(['CocoaDialog', 'standard-dropdown', '--exit-onchange' '--title', 'Previous links', '--text', 'Choose an existing link', '--items'] + choices, stdout=PIPE, env =dialogenv).communicate()[0].split()
31:  
32:  # Insert the link.
33:  if int(chosen[0]) == 1:
34:    sys.stdout.write('[%s][%s]' % (selection, refs[int(chosen[1])][0]))
35:  else:
36:    sys.stdout.write(selection)

Lines 9-11 set up the path to the CocoaDialog executable. It’s included as part of TextMate, so there’s no need to install anything new.

Lines 13-16 define a function that truncates a string if it’s longer than a given value. It’s used in Line 29 to keep the URLs in the menu from getting ridiculously long.

Lines 18-22 get the selected text, if any.

Lines 24-26 get the URLs of all the reference links, excluding Markdown footnotes. The beginning of the regex in Line 26 is particularly nasty looking. There’s a caret to signify the beginning of a line, a caret to complement a character set, and a caret because that’s one of the characters I need to exclude. Brackets are used literally, to define a character set, and as the other excluded character within the character set. Yuck.

Lines 28-30 set up the list of menu items from the found URLs and run CocoaDialog.

Lines 32-36 create the link if a menu item was chosen or return the selected text unaltered if not.

If you don’t want to build the command yourself, you can download it. If I ever get around to turning my other reference link commands into a bundle, I’ll add this one to it.


  1. Brett’s command in the Blogsmith bundle puts them all near the top of the document, just under the headers. Same difference. 

  2. Interestingly, the changes needed were very much like the input flexibility I talked about in my Python fileinput post from a few weeks ago. Had they read Lri’s comment on that article, they would’ve known about Ruby’s ARGF right from the start.