A line-numbering action for Drafts

Among my many deficiencies as a human is my failure to write a review of Drafts 5. It will come eventually, but in the meantime, I’ll put out a few posts on how I’m scripting and configuring Drafts to be my primary text editor on iOS for both short snippets (which is how I’ve used it for years) and longer pieces like reports and blog posts. If you want to see a real review of Drafts 5, I’ll suggest

Because so many of my blog posts include source code with line numbers, I need my text editor to have a line-numbering command. Drafts 5 has built-in a way of displaying line numbers,1 but I need the numbers to part of the actual text. The action I made for doing this is called, with the great imagination I’m known for, Number Lines, and you can get it from the Drafts Action Directory.

The action consists of a single step, which is this script:

 1:  // Number the selected lines (or all lines) with colon and
 2:  // two spaces after each line number.
 5:  // Function for right-justifying text str in width n.
 6:  function rjust(str, n) {
 7:    var strLen = str.length;
 8:    if (n > strLen) {
 9:      var prefix = ' '.repeat(n - strLen)
10:      return prefix + str;
11:    } else {
12:      return str;
13:    }
14:  }
16:  // Get either the current selection or the entire draft.
17:  var sel = editor.getSelectedText();
18:  if (!sel || sel.length==0) {
19:    editor.setSelectedRange(0, editor.getText().length);
20:    sel = editor.getSelectedText();
21:    }
23:  // Break the text into lines and number them.
24:  // Right-justify the numbers and put a colon and
25:  // two spaces after the line number.
26:  var lines = sel.split('\n');
27:  var numLines = lines.length;
28:  var width = Math.floor(Math.log(numLines)*Math.LOG10E) + 1;
29:  var numbered = [];
30:  var lNum;
31:  for (var i=0; i<numLines; i++) {
32:    lNum = i + 1;
33:    numbered.push(rjust(lNum.toString(), width) + ':  ' + lines[i]);
34:  }
36:  // Replace the original text with the line-numbered text.
37:  editor.setSelectedText(numbered.join('\n'));

This is longer than I think it should be, mainly because JavaScript doesn’t have Python’s (or Perl’s or Ruby’s) text-handling capabilities. It doesn’t even have a C-style sprintf. So the rjust function in Lines 6–14 is there just to right justify the line numbers.

Update May 5, 2018 9:56 PM
This tweet from Tim Tepaße let me know that recent versions of JavaScript include a padStart method that will handle the right justification of strings. So the rjust method wasn’t necessary, and line in which it’s used can be changed to

numbered.push(lNum.toString().padStart(width) + ':  ' + lines[i]);

I’ve left the original code here in the blog post, but the Drafts Action Directory has been updated.

This tells me a couple of things:

  1. Code in other actions I’ve written that zero-pads numbers to ensure two digits for month and day numbers can be simplified with padStart (it has an optional parameter for the padding character).
  2. I need to update the JavaScript references I use. The websites that Google steers me to don’t know about padStart, nor does my copy of David Flanagan’s JavaScript: The Definitive Guide. To be fair, I just noticed my copy of Flanagan was published in 2006.

Lines 17–21 get either the selected text or—if no text is selected—the entire draft. This is the text that will have line numbers added to it.

Line 26 splits the text into an array of lines so we can loop through and add line numbers to each one. Line 27 gets the length of the array, numLines, which we’ll use as a limit in the for loop that starts on Line 31.

Before we start the loop, though, we need to know how wide (in characters) the line numbers will be. I could do this by turning numLines into a string and getting its length, but there’s no mathematical fun in that. Instead, in Line 28, I took the integer portion of the base-10 logarithm of numLines and added 1 to it. And because JavaScript doesn’t have a base-10 log function, I had to use the natural log of the numLines and convert it to base 10 by multiplying it by the natural log of 10. Who says junior high math doesn’t come in handy?

Inside the loop, we prefix each line with the right-justified line number, a colon, and two spaces (we’ll get to the colon and the spaces in a minute), and push the resulting string onto the end of the numbered array, which we initialized as empty in Line 29.

With the loop finished, we join up the elements of numbered with a newline character and replace the selection in the draft with the numbered lines.

So why the colon and the two spaces after the line number? Well, partly because I think it looks nice in a plain text file, but mostly because I have a JavaScript/jQuery function here on the blog that takes line-numbered source code formatted that way and styles it so the line numbers are visible but less intrusive.2 The setup is described in this post from way back in 2010.

I’ve been using Drafts 5 for almost two months now (I came in late to the beta), and I’ve been slowing building up and refining a set of actions to help me use it for nearly all of my iOS writing. Some of them are so customized for my way of working that they won’t be immediately useful to anyone else. But I’ll still post them, as they give a sense of what Drafts 5 is capable of, and they can be the starting point for your own scripts.

  1. Strictly speaking, it’s paragraph numbering, but for source code that’s the same as line numbering. 

  2. If you’re reading the RSS feed of this post, you’ll see the line numbers and colons without any styling. You’ll have to go to the actual post to see what I’m talking about.