Roben Kleene

That’s just not true for iPad

In the context of all the conversations about the new iPad Pros replacing Macs, I found these paragraphs from a Daring Fireball article last year very compelling. John Gruber writes:

But put software development aside. I think the bigger problem for the iPad is that there are few productivity tasks, period, where iPad is hardware-constrained. Aldus PageMaker shipped for the Mac in 1985. By 1987 or 1988, it was easy to argue that the Mac was, hands-down, the best platform the world had ever seen for graphic designers and visual artists. By 1991 — seven years after the original Mac — I think it was inarguable. And the improvements in Mac software during those years drove demand for improved hardware. Photoshop, Illustrator, Freehand (R.I.P.), QuarkXpress — those apps pushed the limits of Mac hardware in those days.

That’s just not true for iPad. The iPad is a terrific platform for casual use. I think it’s better than a MacBook for reading and watching video. It’s great for casual gaming. I know plenty of people who much prefer the iPad as a tool for writing. Not because iPad writing apps are more powerful, but rather because they’re simpler, less distracting, and easier to focus upon. None of those are compelling reasons to upgrade an older iPad for a newer more powerful one.

You Don't Need a Mac

Matt Gemmell on the direction the wind is blowing for the future of iOS and the Mac:

But you don’t need a Mac. You need Xcode on the iPad. That’s all. […]

There’s no particular reason you can’t write and debug code on an iPad, and it’s going to happen. Same for Logic, and Final Cut, and Photoshop, and so on. Oh sure, maybe it won’t be those specific apps (though in a lot of cases, it will be), but tasks themselves are always more interface-agnostic than you think.

I see where he’s coming from, and there’s a part of me that agrees, or at least wants to agree. The architecture of iOS is fundamentally more secure and easier to use than macOS, and I love fantasizing about a future where it’s the only OS I ever need.

But the mistake I think he’s making is equating programming to just another task. For computers, it’s not a task, it’s the task; it’s what makes everything else possible. And you still can’t do it on iOS1. Has any platform ever become a first-class programming platform retroactively? They’re usually designed to be bootstrapped as fast as possible.

I’m increasingly convinced that this process of bootstrapping builds features into the nature of platform, and that these features can’t be retroactively changed or added2. And if that’s true, it means the ship has already sailed on iOS ever being a great programming platform. I really hope Apple proves me wrong on this.

  1. It’s such an obnoxious term, but so perfectly apt I can’t help using it: iOS only allows “toy” programming. ↩︎

  2. For examples of where Apple has tried to change the nature of macOS, see Spaces, the Mac App Store (especially sandboxing), replacing “Save As” with “Duplicate” in Mac OS X Lion, and “App Nap” from OS X Mavericks↩︎

`fd` For Find Files

One of the concepts I want to emphasize is the advantage of using small “composable” Unix utilities. To this end, I use rg for all kinds of operations that search or list files. One advantage of this approach is that convenient rules setup in rg, e.g., ignoring .git directories and files in .gitignore, then propagate over every file and search operation I perform.

But something that’s always bothered me is since rg doesn’t support finding directories (understandably), I lose rg semantics when searching for directories, and instead get decades old find semantics, which don’t do smart things like ignoring things that probably should be ignored.

So I was delighted when I followed the ripgrep GitHub issue to the fzf README to fd. A utility that does exactly what I’m looking for, find directories while replicating the smart take on semantics used by rg (and other modern unix utilities).

Here’s an example of my favorite use of this, replacing the built-in fzf zsh widget’s FZF_ALT_C_COMMAND, which enables a key command to fuzzy filter a directory to cd to recursively.


A Quick Command-Line Interface for Safari History

I got so frustrated by not being able to quickly find web pages in my Safari history that I hacked together a simple command-line interface that lets me search my history with fzf.

It comes in two pieces, a shell script that uses the sqlite3 command-line utility to dump the title and URL of each web page:

read -r -d '' select << EOM
SELECT title, url
FROM history_visits
INNER JOIN history_items
ON history_visits.history_item =
ORDER BY visit_time desc
LIMIT 1000;

sqlite3 -noheader -separator $'\t' ~/Library/Safari/History.db \
  "$select" 2>/dev/null \
  | uniq

And a zsh function that pipes its output through fzf:

fzf-safari-history-open() {
  local result=$(safari-history-dump \
	| FZF_DEFAULT_OPTS="-m --reverse --prompt \"Safari History> \" \
	--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS" $(__fzfcmd) +m \
	| cut -f2)
  if [[ -n $result ]]; then
	open "$result"

Here’s what it looks like:


TextMate's Command Composability

For its complexity level, TextMate is the most well-designed application I’ve ever encountered. Featurewise1, it doesn’t compete with Vim2, Emacs, or Sublime Text—but the features it does implement are better designed than anywhere else.

TextMate’s design elegance is exemplified by its command composability. “Composability” here means commands can be chained together to produce cumulative effects that are logical and useful.

For example, lets say you want to change the name of the startTime variable to startDate in this Swift snippet:

struct ProcessInfo: Equatable {
    let identifier: Int32
    let startTime: NSDate
    let commandPath: String
    init(identifier: Int32, startTime: NSDate, commandPath: String) {
        self.identifier = identifier
        self.startTime = startTime
        self.commandPath = commandPath

One approach you could take is the following:

  1. Select startTime.
  2. Press ⌘E to “Use Selection for Find”.
  3. Press ⌘F to bring up the “Find” dialog.
  4. Press ⌥⌘F to “Find All”.
  5. Tab to the “Replace:” field.
  6. Enter startDate.
  7. Click “Replace All”.
  8. Press ⌘W to dismiss the “Find” dialog.

Here’s how TextMate’s command composability can be used accomplish the same goal in less steps, and without any annoying dialog boxes or clicking for “Replace All”3:

  1. Select startTime.
  2. Press ⌘E to “Use Selection for Find”.
  3. Press ⌥⌘F to “Find All”.
  4. Enter startDate.
  5. Enter ⇧⇧ to end multiple cursors.

Here’s what it looks like:

TextMate Demo

The key is the interpretation of ⌥⌘F for “Find All”. When interpreting this command, TextMate considers the context, and performs the most logical action:

  1. The “Find” dialog is not visible.
  2. The find pasteboard is populated (by the ⌘E earlier).

Choosing to initiate multiple cursors.

TextMate’s composability can be demonstrated further by performing the same commands with an active text selection. In that case, only instances of startDate within the text selection are edited. This idea of acting on the current selection or, in the absence of a selection, the whole document, is pervasive throughout TextMate4.

Command composability is a defining trait of well-designed software—learning a piece of software is an investment, and one of the best ways an application can make the most of that investment is to amplify what you can accomplish for the same effort through command composability.

  1. Autocomplete, linter integration, split windows, and ctags are the most problematic missing features. ↩︎

  2. Vim deserves special attention in this post because it also excels at command composability. But Vim has far more design flaws than TextMate. I’d be surprised if anyone can read the chapter on regular expressions in Practical Vim and not come to this same conclusion. ↩︎

  3. “Replace All” does have a keyboard shortcut, ⌃⌘G, but it’s hard to remember. And frankly, OS X dialog boxes don’t work very well when you want to choose any button other than the default (or esc to dismiss). And “Replace All” is too dangerous to make the default. ↩︎

  4. “Filter Through Command…” is my favorite example. ↩︎