Anger, magic, and AppleScript

Yesterday was a day of great frustration in using my Mac. After couple of hours of swearing, I deleted a utility program for what were almost certainly irrational reasons and then wrote an AppleScript to replace its functionality.

I was annotating a set of drawings and photographs of a building using a combination of Preview, OmniGraffle, and Acorn. The work involves a lot of zooming in to make sure the annotations line up with the architectural feature and zooming out to make sure the annotations are nicely spaced and easy to read. The frustrations arose because pinch-to-zoom had stopped working. I did all the usual things: quit and relaunched the programs, unset and reset the pinch-to-zoom feature in System Preferences, and rebooted the computer. And I did these in different sequences, too. For example, I tried unsetting pinch-to-zoom, then rebooting, then resetting pinch-to-zoom. Nothing worked.

I quit BetterTouchTool. This is not an essential utility for me; I’ve been using it lightly for a couple of months but only because it came with Setapp. It seemed logical that an app that redefines how the trackpad behaves could be interfering with pinch-to-zoom, even though it never had before. But quitting BetterTouchTool didn’t work either, even after another reboot.

It was at this point that I descended into irrationality. I uninstalled BetterTouchTool. Now I know what you’re thinking, because I was thinking it, too. There’s no way an app that isn’t running can affect my computer. It’s just a bunch of bits on the hard disk, like any other file. But just the same, I uninstalled BTT, deleted its preferences, and rebooted. And pinch-to-zoom started working again. It’s still working.

Coincidence? Magic? Some weird digital placebo effect? I have no idea, I’m just happy to be able to zoom freely again.

Uninstalling BTT was not a great loss for me. I’m much more of a keyboard shortcut guy than I am a trackpad gesture guy. But there was one gesture I’d built that was useful: it arranged BBEdit and Terminal windows to convenient sizes in the right half of my screen. While I didn’t care how that arranging was triggered, I did miss the rearranging itself. So I wrote an AppleScript to do it and saved it as a Keyboard Maestro macro.

Here’s the macro, which I’ve saved in the Global group and which is triggered by ⌃⌥⌘W:

Arrange BBEdit and Terminal Windows macro

It’s just one step with this AppleScript:

1:  tell application "BBEdit"
2:    set bounds of front window to {1585, 45, 2495, 850}
3:  end tell
5:  tell application "Terminal"
6:    set bounds of front window to {1585, 855, 2495, 1435}
7:  end tell

The window bounds are tuned for my 2012 (non-Retina) 27″ iMac with the Dock on the right side. When it’s run, the BBEdit and Terminal windows look like this:

Rearranged BBEdit and Terminal

I use the space off to the left for reference material as I write or code: drawings, photos, documentation, web pages, whatever.

A more general solution would get the bounds of the whole screen and do some math to figure out the window coordinates. But I wanted a quick solution that would get me back to work, so I resized the windows by hand, used AppleScript and a call to

get bounds of front window

for each app, and then tweaked those results by a few pixels to get the coordinates used in the script.

As for the zooming problem, I have no intention of investigating what happened or why. As long as pinch-to-zoom is working, I’ll leave well enough alone. But if magic works in the Apple world, I may invest in a silver-haired voodoo doll and see if a few well-placed pins will encourage Craig Federighi to improve macOS reliability.

Fear of scripting

Since starting my Twitter hiatus in January, I’ve spent more time on user forums (like those for Drafts, and the Automators podcast) and I’ve seen more evidence for something that’s always puzzled me: fear of scripting.

To be clear, I’m never puzzled when a “regular” user assumes that scripting is beyond them. Programming is so often presented as a complicated and daunting process that mere mortals should never attempt, so it’s not surprising that people who’ve never been exposed to it would think it’s beyond them. What surprises me is when people who know Shortcuts or Keyboard Maestro—people who develop long workflows with branches and loops and regular expressions—balk at a little JavaScript or Python or AppleScript.

I understand that there are only so many hours in a day and whatever time you invest in learning the rudiments of a new language takes time away from other things you’d like to do—or need to do. We all have to make decisions about what is worth our time and what isn’t. I feel this more strongly as I approach 60 and realize that there are things I’d like to learn but will never get to.

But it isn’t the “can I budget the time” objection that bothers me. It’s the “oh, I could never do that” objection. Believe me, most programmers aren’t that bright. Think of how many badly made websites you visit on an average day and realize that there’s a JavaScript programmer who is certainly no smarter than you behind every one of them. If you’re automating things using a visual tool like Shortcuts or Keyboard Maestro or Automator, you already are scripting.

JavaScript variable scoping and Drafts

An interesting problem came up in the user forum for Drafts yesterday. User trbielec was making new actions by stringing together other actions that had already been written, an admirable attempt to use the DRY principle. But he was getting error messages about duplicate variable names. This is a rare case in which a scripting problem is related to honest-to-goodness computer science rather than just an error in logic.

A simple example that demonstrates the problem can be built from two actions, which you can install from the links:

Subroutine Action consists of a single JavaScript step with this code:

let a = "Subroutine";
alert("Greetings from the " + a);

Running this will cause an alert to appear.

Subroutine Action alert

Caller Action has two steps. The first is a JavaScript step with similar code,

let a = "Caller";
alert("Greetings from the " + a);

and the second step is an Include Action step that calls Subroutine Action. When this is run, a “Greetings from the Caller” alert appears as expected,

Caller Action alert

but instead of then seeing the “Greetings from Subroutine” alert, you’ll get an error message saying you can’t create a duplicate variable a.

Variable redefinition error message

The error comes from JavaScript’s variable scoping rules. JavaScript doesn’t allow you to use let to define a variable twice within the same code block. You might think that because the two let statements are in separate actions, they are also in separate blocks, but as trbielec learned, that isn’t the case. Drafts treats all the JavaScript code in an action—even if some of that code is there through an Include Action step—as one big chunk. So in our example, it’s as if we had written

let a = "Subroutine";
alert("Greetings from the " + a);
let a = "Caller";
alert("Greetings from the " + a);

which is clearly illegal.

The solution is pretty simple. Just wrap one (or both) of the two bits of JavaScript in braces:

let a = "Subroutine";
alert("Greetings from the " + a);

The braces define a block, so now the a in Subroutine Action is different from the a in Caller Action and there’s no conflict and no error. Caller Action runs the way you expect, with both alerts appearing in sequence.

I’ve never had a scoping conflict like this in my Drafts actions, and more significantly, Greg Pierce says he’s never seen it arise in the Drafts forums before, so it’s unlikely you will ever put this blog post to practical use. But it was a fun way to divert myself from the work I should be doing.

Quicker linking to apps in Drafts

Today on the Automators forum, there was a question about using Shortcuts to get the current version of an app in the App Store. When I read the answer, from Stephen Millard (@sylumer), I immediately knew I could modify his shortcut to speed up linking to apps, something I do quite often here.

Up until now, this is how I’ve been linking to an app:

  1. Open the App Store app.
  2. Search for the app I want to link to.
  3. Get the app’s URL from the Share Sheet.
  4. Return to Drafts, which is where I’m writing the post, select the text I want to turn into a link (typically the name of the app), and run the Markdown Reference Link action from @pdavisonreiber.

Because the App Store doesn’t open in search mode, this can take half a minute to a minute to do. Even worse is the context shift as I switch from Drafts to the App Store and back. By stealing Stephen’s shortcut and making a Drafts action that glues it to @pdavisonreiber’s action, my workflow has been reduced to

  1. Type the name of the app and select it.
  2. Run the App Store Link action.

which runs in just a few seconds, requires no intervention on my part, and has no context shift other than me watching the screen change from Drafts to Shortcuts and back.

Here’s my adaptation of Stephen’s shortcut. It grabs the App Store URL of the given app and puts it on the clipboard

0 App Store URL Step 00 I won’t be running this from the Share Sheet, but I have to set it up as if I will so it accepts text.
1 App Store URL Step 01 Search the App Store for the given text. We have to have some faith that the search will work.
2 App Store URL Step 02 We also have to have some faith that the app we’re looking for is at the top of the found list.
3 App Store URL Step 03 Get the URL of the app.
4 App Store URL Step 04 Put it on the clipboard so it’s ready to be used in the next step of the Drafts action.

The App Store Link action consists of two steps. The first runs the App Store URL shortcut, passing it the selected text.

App Store Link Shortcut step

The second step runs the Markdown Reference Link action, which uses the URL on the clipboard to turn the selected text into a link.

App Store Link Include Action step

So by doing virtually no work of my own, I have a new Drafts action that will save me upwards of a minute every time I need to link to an app and doesn’t take me out of the flow of writing.