Nathan Grigg

BBEdit search and replace with AppleScript

I have a love-hate relationship with AppleScript. The syntax is annoying, string manipulation is a pain, it is hard to debug, and scripts are stored in a binary format. But I would be incredibly sad if AppleScript went away. Interacting with other programs gives you a lot of power.

A recent example

Following the advice of Bram Moolenaar, I’ve been monitoring tasks that I repeat often which take longer than they could. I noticed that when writing Latex, I often need to change then name of an environment while editing. For example, I might have

    \int_0^x e^{t^2} \, dt

Then I decide that I need to change it to an align* environment or just remove the star from the equation* environment, which means I have to edit both the begin tag and the end tag. Changing both at once will probably only save a few seconds, but those few seconds are saved over and over again, and typing becomes a little less repetitive and a little more productive.

Here is where AppleScript comes in. I need to get the location of the cursor, determine which environment contains it, prompt the user for a new name, make the change, and put the cursor back where it belongs.

The first two of these I could do with Python. BBEdit uses environment variables to pass information about the cursor to cursor position to shell scripts. Python would have no problem doing the searching. But prompting the user and making sure the cursor doesn’t jump due to a change in the length of the document require AppleScript.

The “Change environment” script is a new addition to my Latex BBEdit package, which incorporates lots of Latex related scripts and clippings I’ve made and collected. I’ll explain some of the useful details of the script in this post.

Get and set BBEdit’s cursor location

You can use AppleScript to get the number of characters between the beginning of the document and the current cursor location:

tell application "BBEdit"
    set cursor_loc to characterOffset of selection
end tell

You can move the cursor around with the command select insertion point. In my case, if I replace equation* with align*, I would like to shift the cursor left by 3, so that its relative position stays the same.

tell application "BBEdit"
    select insertion point before character (cursor_loc + diff) ¬
        of document 1
end tell

Search BBEdit document using AppleScript

To figure out which environment contains the cursor, I search backwards from the cursor for a begin, then forward from that point for an end, and then I check to make sure that the cursor is between the two. If it is not, I repeat the process until it is. There is a little extra logic in there to deal with possible nested environments.

A basic search looks like

tell application "BBEdit"
    find "\\\\begin{equation*}" searching in ¬
        characters 1 thru cursor_loc of document 1
end tell

The result is an AppleScript “record” (dictionary) with keys found (was there a match?), found object, and found text. The found object has properties like characterOffset and length and startLine.

Notice the double escaped backslashes. AppleScript interprets each pair of backslashes as a single backslash, and BBEdit does the same. This sort of thing used to drive me nuts, but I’m better at it now.

Of course, things don’t really get exciting until you do a grep search. This one searches backwards from the cursor for any sort of begin and captures the name of the environment.

tell application "BBEdit"
    set match to find "\\\\begin{(.*?)}" searching in ¬
        characters 1 thru cursor_location of document 1 ¬
        options {search mode:grep, backwards: true}

    if found of match then
        set environment to grep substitution of "\\1"
    end if
end tell

Change the text

Here is how you change just one piece of the document

tell application "BBEdit"
    set characters 10 thru 15 of document 1 to "new text"
end tell