October 14, 2025 at 5:49 PM by Dr. Drang
A few weeks ago, I mentioned in a footnote that I have a script that takes a URL on the clipboard and makes a Markdown reference-style link in BBEdit. Actually, I have several link-making scripts for BBEdit. Iāve written about some of them already (including very long ago), but never about all of them. Buckle up.
The BBEdit support folder and packages
My linking scripts are all accessible via the Blogging submenu of BBEditās Scripts menu.1

As you can see, I have several blogging scripts, but weāll focus on just the linking scripts here.
The submenu indicates that the scripts are in the Blogging package, or bundle, which I created to keep all my BBEdit blogging tools in one spot. Rather than trying to explain in words how packages are organized in the BBEdit support folder hierarchy, Iāll show you how I have things arranged:

As you can see, I have them set up in iCloud Drive. They can also be in your Dropbox folder or, if you have no need to share configurations across two or more computers, in ~/Library/Application Support/BBEdit. Full instructions on where and how to set up an application support folder can be found in the BBEdit User Manual.
The funny-looking icon labeled Blogging is the package of interest. For linking, there are two utility scripts in the Resources subfolder and five AppleScripts in the Scripts/Blogging subfolder.
Reference link background
As you can see in the Markdown documentation, there are two kinds of links: inline and reference. Inline links look like this:
the [Markdown documentation](https://daringfireball.net/projects/markdown/syntax#link)
where the text that will become the link is enclosed in brackets and the URL is enclosed in parentheses. Reference links look like this:
the [Markdown documentation][1]
where the link text in brackets is followed by a reference, also in brackets. Somewhere else in the fileāI put them at the bottomāis the reference again, followed by a colon and the URL:
[1]: https://daringfireball.net/projects/markdown/syntax#link
Iām showing the references as numbers because thatās what I use, but the reference string can be anything.
All of my scripts make reference links, incrementing the reference number as more are added.
Utility scripts
The Blogging packageās utility scripts are saved in its Resources subfolder. Two of them are used to make reference links: nextreflink and getreflink.
Because I use numbers in my reference links, new links need to know what the last reference number was so they can increment it. The nextreflink script figures out what the highest existing reference number is and returns the next integer.
python:
1: #!/usr/bin/env python3
2:
3: import sys
4: import re
5:
6: text = sys.stdin.read()
7: reflinks = re.findall(r'^\[(\d+)\]: ', text, re.MULTILINE)
8: if len(reflinks) == 0:
9: print(1)
10: else:
11: print(max(int(x) for x in reflinks) + 1)
It assumes the Markdown source of the file Iām working on will be fed to it via standard input. It uses the regular expression on Line 7 to find all the reference links in the file and collects them in the list reflinks. Because the regex has just one capturing group, the findall function returns a list of just that group. If there are no reference links in the file yet, Line 9 returns 1; otherwise, Line 11 determines the maximum reference number and adds 1 to it.
As weāll see in the next section, nextreflink is used by all the link-making scripts that create new links. But one of the scripts, Old Link, doesnāt make a new link; it presents me with a list of all the links already in the file so I can choose one to use again. The utility script getreflink does that:
python:
1: #!/usr/bin/env python3
2:
3: import sys
4: import re
5: import subprocess
6:
7: # Utility function for shortening text.
8: def shorten(str, n):
9: 'Truncate str to no more than n chars'
10: return str if len(str) <= n else str[:n-1] + 'ā¦'
11:
12: # Read in the buffer and collect all the reference links.
13: text = sys.stdin.read()
14: refRE = re.compile(r'^\[(\d+)\]: +([a-z]+://)?(.+)$', re.MULTILINE)
15: refs = refRE.findall(text)
16:
17: # Create an AppleScript to ask the user for the link
18: choices = [ shorten(x[2], 100) for x in refs ]
19: choiceString = tString = '{"' + '", "'.join(choices) + '"}'
20: firstChoice = f'{{"{choices[0]}"}}'
21: cmd = f'''
22: set theLinks to {choiceString}
23: tell application "System Events"
24: activate
25: choose from list theLinks with title "Choose Reference Link" default items {firstChoice}
26: end tell
27: get item 1 of result
28: '''
29:
30: # Run the AppleScript
31: osa = subprocess.run(['osascript', '-'], input=cmd, text=True, capture_output=True)
32: if osa.returncode == 0:
33: choice = osa.stdout.rstrip()
34: else:
35: sys.exit()
36:
37: # Get the reference number
38: idx = choices.index(choice)
39: print(refs[idx][0])
This oneās got a lot of pieces. The first step, as in nextreflink, is to go through standard input and collect all the reference links. Thatās done in Lines 13ā15. The findall here generates a list of tuples. Each tuple consists of the reference number, the URL scheme (typically https://), and then everything after that. The third item is what weāll use to make the selection.
Before we look at the rest of the code, hereās the prompt presented to the user. The URL is selected from the given list.

The next section of code, Lines 18ā28, builds an AppleScript that makes this āchoose from listā prompt. First, it makes a Python list of the third tuple item from each element of refs. Line 19 turns that list into a string, choices, with the format of an AppleScript list. The first item of the list is going to be the default item; its AppleScript string is built in Line 20. Finally, Lines 21ā28 assemble the AppleScript that will put up the prompt window and collect the result.
Lines 31ā35 run the AppleScript via subprocess.run and osascript. If the user makes a selection, the text of that selection is put into the choice variable. If the user cancels, the script ends with no output.
The last chunk of code, Lines 38ā39, uses choice to figure out the reference number of the selection and prints it to standard output.
Before moving on to the AppleScripts that actually insert the reference links, I want to draw your attention to the shebang lines in the two utility scripts. These are exactly what Iād use if I were writing a script meant to be run from the command line. Scripts that arenāt run from the Terminal2 often donāt get the same environment as those that are. But in one of its many thoughtful touches, BBEdit reads your command line environment directlyāI guess by looking through your various bash or zsh configuration filesāso you donāt have to pull any special tricks to get your scripts to run.
The link-making AppleScripts
BBEdit has a large AppleScript dictionary, and the scripts in this section take advantage of that to move the cursor aroundāessential when writing reference linksāand to behave differently depending on whether text is selected or not. All the scripts are stored in the Blogging subfolder of the Blogging packageās Scripts folder.
Most of the following AppleScripts callāvia the do shell script syntaxāone of the two Python utility scripts above. You may well ask why I needed two languages to get the work done. Two reasons:
- Regular expressions seemed to be the natural way to find the existing reference links, and AppleScript doesnāt have regular expressions. Now itās true that Daniel Jalkut made an AppleScript regex dictionary available (for free!) through his FastScripts utility, but that leads to the second reasonā¦
- Which is that when dealing with text and slicing and dicing lists, I feel much more comfortable working in Python than in AppleScript. Iām happy to work in AppleScript for the cursor control, but I would hate using it for the work done in
nextreflinkandgetreflink. Even thoughgetreflinkitself calls out to AppleScript.
Overall, I found the friction that came with using two languages preferable to the friction Iād run into using AppleScript alone.
With that said, letās explore the AppleScripts themselves.
Clipboard Link
Iām starting with this one because itās the simplest and probably the most obvious. Lots of people have made AppleScripts, Shortcuts, and Keyboard Maestro macros for making Markdown inline links starting with a URL on the clipboard. This script is one level of complexity up from that.
If text is selected, this turns that text into a reference link to the URL on the clipboard. If no text is selected, it creates an empty reference link where the cursor was. In both cases, the new reference link with an incremented number is put at the bottom of the text and the cursor is moved to a convenient location. Hereās the code:
applescript:
1: -- Get the path to the Resources directory in the package.
2: tell application "Finder"
3: set cPath to container of container of container of (path to me) as text
4: end tell
5: set rPath to (POSIX path of cPath) & "Resources/"
6:
7: set myURL to the clipboard
8:
9: tell application "BBEdit"
10: set oldClipboard to the clipboard
11: set the clipboard to contents of front document as text
12: set myRef to do shell script "pbpaste | " & quoted form of (rPath & "nextreflink")
13: set the clipboard to oldClipboard
14:
15: if length of selection is 0 then
16: -- Add link with empty text and set the cursor between the brackets.
17: set curPt to characterOffset of selection
18: select insertion point before character curPt of front document
19: set selection to "[][" & myRef & "]"
20: select insertion point after character curPt of front document
21:
22: else
23: -- Turn selected text into link and put cursor after the reference.
24: add prefix and suffix of selection prefix "[" suffix "]" & "[" & myRef & "]"
25: select insertion point after last character of selection
26: end if
27:
28: -- Add the reference at the bottom of the document and reset cursor.
29: set savePt to selection
30: select insertion point after last character of front document
31: set selection to "[" & myRef & "]: " & myURL & return
32: select savePt
33: activate
34: end tell
Lines 2ā5, which weāll use in most of the scripts in this section, get the path to the Resources directory by using AppleScriptās clever path to me construction. The unfortunately long container to container to container bit comes from the folder hierarchy shown above. Our AppleScripts are in the Blogging subdirectory of the Scripts directory of the Blogging package, so we have to go up three levels before going down a level into the Resources directory. Thereās also a conversion in Line 5 from AppleScriptās native colon-separated path format to Unixās slash-separated path format.
Line 7 just puts the URL on the clipboard into the variable myURL. This wasnāt necessary, but it creates a parallelism between this AppleScript and the others.
The rest of the script manipulates BBEdit to make a reference link. Lines 10ā13 pass the text of the front BBEdit document to the getnextref utility script via the clipboard and the pbpaste command. The reference number returned is stored in the myRef variable. Lines 10 and 13 are a common AppleScript construct used to save and restore the original clipboard contents.
If no text is selected, Lines 17ā20 insert an empty set of brackets followed by the bracketed reference number, e.g., [][1], at the cursor. The cursor is then moved between the empty brackets so I can type in the link text.
If text is selected, itās taken to be the link text. Lines 24ā25 surround the selected text with brackets, put the bracketed reference number after it, and put the cursor after the last bracket.
The last step is to add the reference to the bottom of the document. Lines 29ā32 save the current cursor position in savePt, jump to the end of the text, insert the reference in the form
[1]: https://myurl.com
and then move back to savePt. Because cursor positions are counted from the beginning of the document, adding text to the end doesnāt change the position savePt refers to.
Safari Front Link
This is the one I use most often. Itās at the top of the Blogging submenu and has been assigned the simplest keyboard shortcut: āL.
It works the same way as Clipboard Link, except it gets the URL from the frontmost Safari tab instead of the clipboard. You might well ask why I bother with Safari Front Link when I have Clipboard Link. I could, after all, select the URL in Safari, put it on the clipboard, and then run Clipboard Link. My answer is that I donāt want to perform those two extra steps when I donāt have to.
Hereās the code:
applescript:
1: -- Get the path to the Resources directory in the package.
2: tell application "Finder"
3: set cPath to container of container of container of (path to me) as text
4: end tell
5: set rPath to (POSIX path of cPath) & "Resources/"
6:
7: tell application "System Events"
8: set pNames to name of every process
9: if "Safari" is in pNames then
10: try
11: tell application "Safari" to set myURL to the URL of the front document
12: on error
13: do shell script "afplay /System/Library/Sounds/Funk.aiff"
14: return
15: end try
16: else
17: do shell script "afplay /System/Library/Sounds/Funk.aiff"
18: return
19: end if
20: end tell
21:
22: tell application "BBEdit"
23: set oldClipboard to the clipboard
24: set the clipboard to contents of front document as text
25: set myRef to do shell script "pbpaste | " & quoted form of (rPath & "nextreflink")
26: set the clipboard to oldClipboard
27:
28: if length of selection is 0 then
29: -- Add link with empty text and set the cursor between the brackets.
30: set curPt to characterOffset of selection
31: select insertion point before character curPt of front document
32: set selection to "[][" & myRef & "]"
33: select insertion point after character curPt of front document
34:
35: else
36: -- Turn selected text into link and put cursor after the reference.
37: add prefix and suffix of selection prefix "[" suffix "]" & "[" & myRef & "]"
38: select insertion point after last character of selection
39: end if
40:
41: -- Add the reference at the bottom of the document and reset cursor.
42: set savePt to selection
43: select insertion point after last character of front document
44: set selection to "[" & myRef & "]: " & myURL & return
45: select savePt
46: activate
47: end tell
The only difference between this and Clipboard Link is Lines 7ā28, which get the URL of Safariās frontmost tab and save it to the variable myURL. Specifically, this is done in Line 11. The rest of this chunk of code is all about error handling. If Safari isnāt runningāor if it doesnāt have a window, or if that windowās frontmost tab is emptyāthe script will play an error sound and stop.
Safari Choose Link
Sticking with the subject of getting links from Safari, letās consider the situation in which I know I have a Safari tab open to the page I want to link to, but itās not in the front tab. The Safari Choose Link script presents a list of all the tabs in Safariās front window for me to choose from (I very rarely have more than one Safari window open). It looks like this:

The pages are presented according to their titles. Initially, none of them are selected, but as soon as I select oneāwhich I can do with the up and down cursor keysāthe OK button will activate. From this point, the script acts just like Safari Front Window.
Hereās the code:
applescript:
1: -- Get the path to the Resources directory in the package.
2: tell application "Finder"
3: set cPath to container of container of container of (path to me) as text
4: end tell
5: set rPath to (POSIX path of cPath) & "Resources/"
6:
7: on shortened(s)
8: if length of s > 40 then
9: set s to (characters 1 thru 35 of s as text) & "ā¦"
10: end if
11: return s
12: end shortened
13:
14: tell application "System Events"
15: set pNames to name of every process
16: if "Safari" is in pNames then
17: try
18: tell application "Safari"
19: -- Initialize
20: set tabNames to {}
21: set tabURLs to {}
22:
23: -- Collect the tab names and URLs from the top Safari window
24: set topWindow to window 1
25: set topTabs to every tab of topWindow
26: repeat with t in topTabs
27: set end of tabNames to my shortened(name of t)
28: set end of tabURLs to URL of t
29: end repeat
30: end tell
31:
32: -- Display a list of names for the user to choose from
33: activate
34: choose from list tabNames with title "Safari Tabs"
35: set nameChoice to item 1 of result
36: repeat with tabNumber from 1 to the count of tabNames
37: if item tabNumber of tabNames is nameChoice then
38: set myURL to item tabNumber of tabURLs
39: exit repeat
40: end if
41: end repeat
42: on error
43: do shell script "afplay /System/Library/Sounds/Funk.aiff"
44: tell application "BBEdit" to activate
45: return
46: end try
47: else
48: do shell script "afplay /System/Library/Sounds/Funk.aiff"
49: return
50: end if
51: end tell
52:
53: tell application "BBEdit"
54: set oldClipboard to the clipboard
55: set the clipboard to contents of front document as text
56: set myRef to do shell script "pbpaste | " & quoted form of (rPath & "nextreflink")
57: set the clipboard to oldClipboard
58:
59: if length of selection is 0 then
60: -- Add link with empty text and set the cursor between the brackets.
61: set curPt to characterOffset of selection
62: select insertion point before character curPt of front document
63: set selection to "[][" & myRef & "]"
64: select insertion point after character curPt of front document
65:
66: else
67: -- Turn selected text into link and put cursor after the reference.
68: add prefix and suffix of selection prefix "[" suffix "]" & "[" & myRef & "]"
69: select insertion point after last character of selection
70: end if
71:
72: -- Add the reference at the bottom of the document and reset cursor.
73: set savePt to selection
74: select insertion point after last character of front document
75: set selection to "[" & myRef & "]: " & myURL & return
76: select savePt
77: activate
78: end tell
The new parts of this script start with the shortened function on Lines 7ā12. It clips the given string; weāll use this later to keep page titles at a manageable length in the Safari Tabs window.
Lines 18ā29 get the titles of the Safari tabs, shorten them if appropriate, and put them in the list form needed by choose from list. In parallel, it makes a list of all the associated URLs. These lists are called tabNames and tabURLs, respectively.
Lines 34ā41 then ask the user to choose from the list of titles and figure out the URL that goes with the chosen title. Thatās put into the variable myURL.
The rest of Lines 14ā51 are error handling, and Lines 53ā78 are the same cursor-handling code used in Clipboard Link and Safari Front Link.
All Safari Tab Links
Another Safari script? Yes, but this one is really short.
Sometimes I know that every tab in Safari is going to be linked to in the post Iām writing. In that situation, why not just add links to all the tabs to the bottom of the document right now? Iāll then make links to them in the body of the text using the Old Link script, which weāll cover next.
Hereās the AppleScript for All Safari Tab Links:
applescript:
1: set n to 1
2: set refs to ""
3: tell application "Safari"
4: repeat with thisTab in (every tab of front window)
5: set refs to refs & "[" & n & "]: " & (URL of thisTab) & linefeed
6: set n to n + 1
7: end repeat
8: end tell
9:
10: tell application "BBEdit"
11: set savePoint to selection
12: select insertion point after last character of front document
13: set selection to refs
14: select savePoint
15: end tell
I told you it was going to be short. Lines 1ā8 build the reference link text one line at a time by looping through the tabs. When the loop is done, the variable refs contains text like this:
[1]: https://daringfireball.net/projects/markdown/syntax#link
[2]: https://leancrew.com/all-this/2021/05/flowing-markdown-reference-links/
[3]: https://leancrew.com/all-this/2012/08/markdown-reference-links-in-bbedit/
[4]: https://leancrew.com/all-this/man/man1/pbpaste.html
[5]: https://redsweater.com/blog/3822/fastscripts-3-1-streamlined-regular-expressions
Lines 10ā15 then save the cursor location, jump to the bottom of the document, insert refs, and jump back to the saved location. Thatās it. Because this script is intended to be run before I start writing, it can start the reference numbering at 1, and thereās no need to call nextreflink.
Old Link
Finally, hereās the script I run when a link is already in the list at the bottom of the file. Now, thereās nothing wrong with repeating links. Having, for example, a list like this at the bottom of a Markdown file,
[1]: https://daringfireball.net/projects/markdown/syntax#link
[2]: https://leancrew.com/all-this/2021/05/flowing-markdown-reference-links/
[3]: https://daringfireball.net/projects/markdown/syntax#link
[4]: https://leancrew.com/all-this/2012/08/markdown-reference-links-in-bbedit/
[5]: https://leancrew.com/all-this/man/man1/pbpaste.html
[6]: https://daringfireball.net/projects/markdown/syntax#link
[7]: https://redsweater.com/blog/3822/fastscripts-3-1-streamlined-regular-expressions
with the Daring Fireball URL associated with a handful of reference numbers, will produce the same HTML as a list of unique URLs. But I canāt imagine anyone wanting a list like that. To me, the Markdown should look as nice as the resulting HTML. If I have several links to the same URL, they should have the same reference number in the body of the text. Hence the Old Link script.
applescript:
1: -- Get the path to the Resources directory in the package.
2: tell application "Finder"
3: set cPath to container of container of container of (path to me) as text
4: end tell
5: set rPath to (POSIX path of cPath) & "Resources/"
6:
7: tell application "BBEdit"
8: set oldClipboard to the clipboard
9: set the clipboard to contents of front document as text
10: set myRef to do shell script "pbpaste | " & quoted form of (rPath & "getreflink")
11: set the clipboard to oldClipboard
12:
13: if myRef is not "" then
14: if length of selection is 0 then
15: -- Add link with empty text and set the cursor between the brackets.
16: set curPt to characterOffset of selection
17: select insertion point before character curPt of front document
18: set selection to "[][" & myRef & "]"
19: select insertion point after character curPt of front document
20:
21: else
22: -- Turn selected text into link and put cursor after the reference.
23: add prefix and suffix of selection prefix "[" suffix "]" & "[" & myRef & "]"
24: select insertion point after last character of selection
25: end if
26: end if
27: activate
28:
29: end tell
Lines 2ā5 are the same as most of the other scripts. Lines 8ā11 are similar to the other scripts, but Line 10 calls getreflink instead of nextreflink. That lets me choose the value of myRef from the list of URLs already being used. The remainder of the script is the same as Clipboard Link, Safari Front Link, and Safari Choose Link.
Final thoughts
Iām not providing a download link to my Blogging package. It contains a lot of other stuff thatās specific to ANIAT and my publishing system. And if youāre really interested in using any of these scripts, a little copying and pasting wonāt do you any harm.
Normally, BBEdit puts the scripts in alphabetical order in the menu, but you can get around that by prefixing the name of your script with a number followed by a closing parenthesis. BBEdit will then arrange the menu in numerical order according to the prefixes but wonāt include the prefixes in the menu items. So my Scripts folder in the Blogging package has files with the names
00)Safari Front Link.scpt
01)Old Link.scpt
02)Clipboard Link.scpt
04)Safari Choose Link.scpt
05)All Safari Tab Links.scpt
among others.
The order of the scripts in the menu reflects my sense of how often I use them, even though All Safari Tab Links is the only one I call from the menu instead of via a keyboard shortcut. Itās by far the least used, and thereās no need for a quick way to run it. All the keyboard shortcuts use āL; the added modifier keys were chosen either at random or through a process I have no memory of.