LaTeX via Drafts

As I said in my last post, I often write reports on both Mac and iPad, switching between the two according to whichever is more convenient to work on. As the writing progresses, the text is kept in both Drafts and a file in Dropbox, which are kept in sync by the Drafts actions described in that post. My reports are written in LaTeX, which have to be compiled into a PDF. Until recently, I did the compilation either directly on a Mac via commands entered in a Terminal window or indirectly on a Mac via commands entered into Prompt, the iOS app that lets me connect to my Macs via SSH. But this week, I created a Drafts Action/Shortcut hybrid that allows me to bypass Prompt and compile LaTeX files from Drafts.

I’ve never set up a similar workflow on my Mac to allow me to compile LaTeX from within BBEdit, because I’ve never seen much advantage in doing so. On a Mac, I can always have a Terminal window ready to run the compilation command. On an iPad, doing the equivalent with Prompt isn’t as easy. Unless Prompt is an active app, it will time out after a few minutes—this is an iOS thing, something the folks at Panic can’t get around. Which means if I’m writing in Drafts (or any editor, for that matter), running the LaTeX compilation command in Prompt is a juggling act. I either have to keep Prompt open as the other app in Split View, which means I have to periodically replace Drafts in Split View with Safari, Dropbox, Books, or whatever apps I’m refering to as I write, or I have to relaunch Prompt, log in, and cd to the right directory every time I want to compile to see how the report looks.

But no more. With the PDFLaTeX action, I can keep Drafts in one pane, the research app in the other, and compile with a menu selection or keystroke combination.

The action has two steps: a Script step that figures out the directory and filename and a Run Shortcut step that sends that information to a Shortcut that connects to my computer and compiles the LaTeX into a PDF.

Here’s the script:

javascript:
 1:  // Get the folder and filename from the dbox line.
 2:  var d = draft.content;
 3:  
 4:  // Regex for fileline.
 5:  var fileRE = /dbox:(.+)$/m;
 6:  
 7:  if (fileRE.test(d)) {
 8:    // Get the path. Add a slash to the front if I forgot.
 9:    var fileline = d.match(fileRE);
10:    var flStart = d.search(fileRE);
11:    var path = fileline[1];
12:    if (path[0] != '/') {
13:      path = '/' + path;
14:      editor.setTextInRange(flStart + 5, 0, '/');
15:    }
16:    // Split the path into directory and filename and make a dictionary.
17:     parts = path.split("/")
18:     var fileinfo = {"directory":"~/Dropbox" + parts.slice(0, -1).join("/"),
19:                 "filename":parts.slice(-1)[0]};
20:    
21:    // Create a template tag from the dictionary.
22:    draft.setTemplateTag("fileinfo", JSON.stringify(fileinfo))
23:   
24:  } else {
25:    alert("No dbox line");
26:  }

Recall that I put a dbox line in my drafts that gives the path to the Dropbox file where the draft is also saved. In a LaTeX file, the dbox line is a comment that looks like this:

% dbox:/projects/test/report/report.tex

Most of the script has been recycled from my earlier script that saves a draft to Dropbox. You can go to that post for the explanation of how the dbox line is parsed. I’ll concentrate here on the new stuff.

Line 17 splits the path into a list of its components: subdirectories and the filename. Lines 18–19 then creates a JSON dictionary. The directory item is the reconstructed path, starting with the Dropbox folder in my home folder. The filename item is exactly what you think it is.

Line 22 creates a template tag named fileinfo for this draft. The value of the tag is the string form of the JSON dictionary created in Lines 18–19. We’ll use this tag in the next step to pass information to a Shortcut.

The Run Shortcut step looks like this:

Run Shortcut step

There’s not much to it. The Shortcut it runs is also named PDFLaTeX, and the text passed to it is the value of the fileinfo tag created in Line 22 of the script above.

Here’s how the PDFLaTeX shortcut is defined:

PDFLaTeX shortcut

The first step parses the text passed to the shortcut into a dictionary. The second step logs into my computer via SSH and runs the following three commands

export PATH=$PATH:/Library/TeX/texbin
cd <Dictionary:directory>
latexmk -pdf -interaction=nonstopmode <Dictionary:filename>

where the things inside the angled bracket are magic variables that extract the given items from the dictionary created in the first step.

For reasons I don’t quite understand, SSHing into my computer through a Shortcut isn’t exactly like doing it via Prompt.1 Most significantly, the PATH environment variable doesn’t include the directory where the various TeX/LaTeX commands are kept. So the purpose of the first line is to add that directory to the PATH.

The second line changes to the directory where the LaTeX file is saved, and the third line does the compilation via latexmk.

The latexmk command is a Perl script that gets installed along with MacTeX. It’s job is to automate the compilation of LaTex files into the final output. If you’re a LaTeX user, you know that generating output often requires two or more steps as bibliographies get built and floating figures move from one page to another; latexmk figures out which commands need to be run and runs them as often as necessary until everything is in its final state. The -pdf option tells latexmk to generate a PDF and the -interaction=nonstopmode option tells it to keep going even if there’s an error in the source files.

I have the action bound to the ⇧⌘P keystroke combination. When I run it, Shortcuts appears on the screen while the shorcut runs. If the compilation is successful, Shortcuts disappears and the screen goes back to Drafts and the research app. If there’s a LaTeX error—a misspelled command or an unclosed brace—Shortcuts stays on the screen displaying the error message.

PDFLaTeX error message

As it stands, the error message displayed is pretty worthless. The full error message from latexmk, which has what we need, is simply too long to fit in the dialog box and the good part gets lost in the truncation. I may try to extract the actual error from the log file and display that instead.

Even with a crummy error message, this is better than juggling apps or relaunching Prompt.


  1. I suspect it has something to do with whether or not I’m logging in with an interactive shell. I could probably avoid this line if I had a better understanding of how and when bash’s various dotfiles are invoked. But the PATH of least resistance (I slay me) was to update the environment variable.