Extending BBEdit with AppleScript

In my previous article where I speculated on when Bare Bones will release BBEdit 12, towards the end of the article I made a statement comparing BBEdit to my other most-used text editor, Sublime Text:

Whenever I switch to Sublime Text, I miss the “look and feel” of BBEdit (which is important, I think, for someone who spends most of their time with a text editor), but when I’m in BBEdit I miss Sublime Text’s editing capabilities.

What I forgot to mention is that BBEdit can be extended to sort of mold it into the text editor you want it to be (to a certain extent, of course). I just haven’t explored that aspect of it just yet—until now, that is.

And when I mentioned missing Sublime’s “editing capabilities”, I’m not talking about some crazy text wizardry that BBEdit happens to not support. I’m talking about some pretty basic things by modern text editing standards:

  1. ⌘ + ⇠ behavior should move the cursor to just before the first word of the current line (as Sublime and just about every modern programming text editor on macOS behaves), not character 0 of the line as BBEdit does.
  2. There should be a keyboard shortcut to select the word under the cursor (in Sublime it is ⌘ + D)
  3. Repeating the keyboard shortcut for selecting a word should initiate multiple-cursor selection on the same word.

Number 1 is really hard for me to live without. I’m just so used to editors behaving consistently in this area that BBEdit’s behavior is too jarring to get used to—I happen to use this key combination a lot. In BBEdit, I have to do ⌘ + ⇠ and then an ⌥ + ⇢ for the same behavior. I also often use this in conjunction with the Shift key to select the line (up to the cursor) without the leading whitespace. With the amount of keyboard-foo this all requires, I might as well give up and use the mouse.

As for number 2, selecting a word comes in handy when you want to do number 3 (multi-cursor select), but also for simpler things like queuing a word for “Use Selection for Find” (Ctrl + E). The fastest way to replicate this in BBEdit is to press ⌥ + ⇠ and then Shift + ⌥ + ⇢, which is a lot more than just the Ctrl + D that I have years of muscle memory behind.

The third one—multi-cursor selection—is something I think future versions of BBEdit will include, because I’m sure it’s a highly requested feature (I think I even saw a post on BBEdit Talk where someone from Bare Bones confirmed it’s coming eventually, but I can’t find it). Anyway, it’s something I can wait on so it’s more of a “nice to have” at this point.

And that’s it! With those three things, my text editing is able to keep up with my thinking and I’m able to be comfortable and highly productive. Like I said, very simple. But unfortunately, BBEdit fails at all three… out of the box.

AppleScript to the Rescue

I really dislike AppleScript. In the past, anything having to do with BBEdit and AppleScript I just instinctively avoided. Since Yosemite, macOS has officially supported JavaScript for similar tasks, but the language is only half the battle. The available APIs are something I find very difficult to get much useful, complete information on.

Anyway, if I’m going to make BBEdit my primary text editor, I should bite the bullet and explore how I can extend it to behave in a way that makes me more comfortable, despite what language is used to do it.

Moving the Cursor

The first task was to find a way to move the cursor to the first non-whitespace character of the current line. It sounds really simple, but with not much prior experience with AppleScript, I was having a lot of trouble figuring this one out. After several tries, it was beginning to look like this is something that’s just not possible.

Then, in a moment of sheer luck, I came across an article published way back in 2010 by Kendall Conrad that provides two AppleScripts: one for moving the cursor to the correct position (first non-whitespace character), and another for selecting the line up to the cursor position minus the line’s leading whitespace—my exact two use-cases!

Here are the two AppleScripts, which need to be placed in ~/Library/Application Support/BBEdit/Scripts to appear in the Scripts menu:

Smart Home Move

Moves the cursor to the first non-whitespace character of the current line.

(*
  Smart Home Move
  Kendall Conrad of Angelwatt.com
  Acts like IDE home key. First will go to right before first visible character on line. Hit a second time will go to true home position of line.
  Created: 2010-07-31
  Updated: 2012-06-03
*)
tell application "BBEdit" to tell front window
  -- Grab line number and offset of text cursor
  tell the selection to set {_n, _selectionstart} to {startLine, characterOffset}
  -- Grab offset of line in the document and the contents of the line
  tell line _n to set {_linestart, _line} to {characterOffset, contents}
  set _cursor to _selectionstart - _linestart
  set _smart to 0
  -- Go through content of line looking for smart home position
  set _len to count _line
  -- Ensure line is not empty
  if _len = 0 then return
  -- Traverse line to find first non-whitespace
  set _nbsp to ASCII character 202
  set theResult to find "(^[\\s" & _nbsp & "*#+-]*)" options {search mode:grep} searching in line _n
  if found of theResult then
    set _smart to length of (found text of theResult)
  end if
  -- Only true if there was no starting whitespace
  if _smart = 0 then
    set _smart to 1
    set _cursor to 1
  end if
  -- Check if not at smart position already
  if _cursor is not _smart then -- Go to smart home
    select insertion point after character _smart of line _n
  else -- Just go to start of line
    select insertion point before line _n
  end if
end tell

Smart Home Select

Selects text on the current line up to the current cursor position, minus the leading whitespace.

(*
  Smart Home Select
  Kendall Conrad of Angelwatt.com
  Acts like IDE home key along with selection. First will go to right before first visible character on line. Hit a second time will go to true home position of line.
  Created: 2010-08-05
  Updated: 2010-08-06
*)
tell application "BBEdit" to tell front window
  -- Grab line number and offset of text cursor
  tell the selection to set {_n, _selectionstart} to {startLine, characterOffset}
  set _selectLength to length of (contents of selection as text)
  if (_selectLength) > 0 then
    set _selectionstart to _selectionstart + _selectLength
  end if
  -- Grab offset of line in the document and the contents of the line
  tell line _n to set {_linestart, _line} to {characterOffset, contents}
  set _cursor to _selectionstart - _linestart
  if _cursor = 0 then return
  set _smart to 0
  -- Go through content of line looking for smart home position
  set _len to count _line
  -- Ensure line is not empty
  if _len = 0 then return
  -- Traverse line to find first non-whitespace
  set _nbsp to ASCII character 202
  set theResult to find "(^[\\s" & _nbsp & "*#+-]*)" options {search mode:grep} searching in line _n
  if found of theResult then
    set _smart to length of (found text of theResult)
  end if
  -- Ensure smart is not past cursor
  if _smart > _cursor then set _smart to _cursor
  -- Check if smart position is where cursor is, if so select from start of line
  if _smart is _cursor or (_cursor - _selectLength = _smart) then
    select (characters (_selectionstart - _cursor) through (_selectionstart - 1))
  else -- Select from smart position
    select (characters (_selectionstart - _cursor + _smart) through (_selectionstart - 1))
  end if
end tell

Because I find AppleScript so hard to find any info on, I likely never would have came up with the elaborate scripts needed for this functionality. The good news is, these scripts will serve as good BBEdit AppleScript references in the future. Thank you, Kendall Conrad.

Select Word

This one was a little easier to find, because I had come across it before. Way back in 2003, John Gruber of Daring Fireball wrote about a Select Word AppleScript for BBEdit, which he found elsewhere and modified, and I made some slight edits as well:

tell application "BBEdit" to tell front window
    set sel_offset to characterOffset of selection
    select (last word whose characterOffset ≤ sel_offset)
end tell

At some point, this AppleScript stopped working, because I had tried it a few years ago to no avail, but thankfully it appears to work again in BBEdit 11. Anyway, that’s two out of three down.

Multiple-Cursor Selection

For the last item, unfortunately, there’s no way to extend BBEdit to do this one. Oh well. At least with the three other scripts, I think I can wait until Bare Bones decides to add multi-cursor selection to BBEdit (if I’m lucky, it’ll be in version 12).

Keyboard Bindings

Now that the scripts are in place to get me most of my needed functionality, they should be bound to some keyboard shortcuts so the menu item doesn’t need to be invoked with the mouse, which would defeat the purpose of this exercise.

Here are the keyboard bindings I used:

  • ⌘ + ⇠ for “Smart Home Move”, which required me to re-map “Apply to Left” (I personally don’t ever use that anyway).
  • ⌘ + Shift + ⇠ for “Smart Home Select”.
  • ⌘ + D for Select Word (re-mapped ‘File > Open File by Name…’ to ⌘ + P).

I mapped the keys to the same as the Sublime Text counter-parts, since that’s what I’m used to. It didn’t take too much re-mapping of the defaults, and as a bonus, ‘Open File by Name…’ is now also bound to the equivalent Sublime keyboard shortcut.

Although I wasn’t able to extend BBEdit to do everything I wanted, the most important things were able to be addressed and I’m already feeling a lot more comfortable. The next time I’m tempted to switch away due to missing functionality, I’ll check to see if BBEdit can be extended to do what I want first.

HostGator $3.96

$3.96 for web hosting – used by jonbeebe.net. Get started today.