Nathan Grigg

New line after punctuation, revisited

A while back, I wrote about ending lines at natural breaks, like at the end of phrases and sentences. In Latex documents, code documentation, git commits, and other situations where I am writing prose but “soft wrapping” is undesirable, this makes revising and rearranging the text much easier. My favorite part is that I no longer have to worry about refilling the lines every time I make a change.

In that post, I linked to a “new line after punctuation” BBEdit AppleScript I wrote. Some time later, I realized that using AppleScript to run BBEdit searches can be way more efficient than pure AppleScript or even Python alternatives. I thought I would use this to extend my new line script to also handle some math-breaking heuristics. That failed because it got too complicated for me to predict what it was going to do. The most important thing about text editor commands is that they be completely predictable so they don’t interrupt your editing flow.

Still, using BBEdit’s find command simplified my new line script and made it one-third as long. I thought it would be another helpful example of how to script BBEdit, so I’m am posting it here. If you are looking for other examples of this kind of script, Oliver Taylor recently posted a collection of helpful cursor movement scripts, most of which use BBEdit’s find function in a similar way.

The new “new line after punctuation”

 1 tell application "BBEdit"
 2     -- save location of current cursor
 3     set cursor to selection
 4     set cursor_char to characterOffset of cursor
 6     tell document 1
 7     set search to find "^([ \\t]*)(.*)([.,!?;:])[ \\t]+" ¬
 8         searching in text 1 ¬
 9         options {search mode:grep, backwards:true} without selecting match
11     if found of search and startLine of found object of search ¬
12         is equal to startLine of cursor then
13         -- found punctuation, insert return
14         set replacement to grep substitution of "\\1\\2\\3\\r\\1"
15         set diff to (replacement's length) ¬
16         - (length of found object of search)
17         set contents of found object of search to replacement
18     else
19         -- no punctuation, just insert a return here
20         set search to find "^[ \\t]*" searching in text 1 ¬
21         options {search mode:grep, backwards:true} without selecting match
22         if found of search and startLine of found object of search ¬
23         is equal to startLine of cursor then
24         set indent to contents of found object of search
25         else
26         set indent to ""
27         end if
28         set diff to (length of indent) + 1
29         set contents of character cursor_char to ¬
30         ("\r" & indent & contents of character cursor_char)
31     end if
33     -- adjust cursor to keep it in the same relative location
34     select insertion point before character (cursor_char + diff)
35     end tell
36 end tell

We begin by telling BBEdit to get the selection object. This contains information about the cursor location if no text is selected. For convenience, we save the characterOffset of the selection, which is the character count from the beginning of the document to the cursor location.

The heavy lifting is done by the grep search in line 7. Here it is with undoubled backslashes, which had been escaped for AppleScript.

^([ \t]*)(.*)([.,!?;:])[ \t]+

The ^ anchors the search to the beginning of a line. The ([ \t]*) captures any combination of spaces and tabs to \1. The (.*) captures as many characters as possible to \2. The ([.,!?;;]) captures a single punctuation mark to \3. The final [ \t]+ requires one or more whitespace characters after the punctuation and captures this whitespace to the search result, but not to any particular group. This is so I don’t insert a new line where there wasn’t already whitespace, and also to throw away whatever whitespace was there.

Lines 8 and 9 search backwards from the current cursor position. If this command (and many that follow) weren’t inside a tell document 1 block, I would need text 1 of document 1 instead of text 1. If you allow BBEdit to select the match, you can see selection flashing while the script runs, which I don’t like. Also, it messes up the cursor location, which will change the result of future searches within the script.

Lines 11 and 12 make sure the script found something on the current line. If so, line 14 uses BBEdit’s grep substitution command to form a string with the whitespace, followed by the line up to the punctuation, followed by the punctuation, a return, and then the whitespace repeated. Line 17 does the replacement.

Lines 20 through 31 deal with the case that no punctuation is found. This just inserts a return at the current cursor location, matching the current line’s indentation.

Line 34 moves the cursor to a new location to make up for the text that has been added.

This script is also available as a gist. I have it assigned to the keyboard shortcut Control+Return.