Mac productivity tips for developers

Working faster

Software developers spend hour after hour on their machines, so it’s worth spending a little time improving common workflows now and then.

The time you can spend on this is virtually unbounded, but I have found there are a few tricks that many people miss—a handful of high-value improvements that can go a long way without hours of investment.

Some of these improvements require extra software, (all of it free and open source) and there’s a section at the bottom to set that up when needed.

1: The “Hyper” key + transform Caps Lock into Escape

You know what would be great? Having an extra modifier key open for whatever we want.

We can make use of Steve Losh’s idea of emulating the extra “Hyper” key introduced by the Space Cadet keyboard by defining Hyper as control+option+command+shift. Since no sane application will expect a user to hold all those keys at once, we can effectively create a new modifier key.

My primary use for Hyper is machine-global shortcut keys, especially for window management with Hammerspoon.

Vim users often remap caps lock to Escape to save their pinky finger some pain. We are going to do that too–but only when tapped. That way, we can user Hyper and Escape on the same key, without them interfering.

Setup

First install Karabiner Elements.

Then, install this karabiner configuration.

Alternatively, you can download this file and add it by hand to the complex modifications section of ~/.config/karabiner/karabiner.json .

Now that that’s done, it’s time to set up some shortcuts that use Hyper in order to ease window management.

2: Managing windows with Hammerspoon

Hammerspoon is a macOS swiss army knife. I mostly use it for window management, not unlike other tools like Divvy or SizeUp. It lets you arrange, resize, switch between, and open applications and windows on one or more monitors.

By setting up a few basic commands in Hammerspoon, switching between and resizing windows in macOS becomes much faster. I have a simple set of related commands that are easy to learn and use, but improve my daily efficiency manyfold.

First, I have the 10-15 most common applications I use bound to Hyper + a single key, such as Hyper+space to open my editor. Next, I have shortcuts for window movement and resizing for the active window: full-screen, two half-screens, four quarter-screens, one to cycle the application between monitors, and one to cycle between instances of the same application.

Mastering these commands is enough to work extremely efficiently, but I have a few others included that I find useful as well.

Setup

First install Hammerspoon. Then open up ~/.hammerspoon/init.lua and add this:

-- Mike Solomon @msol 2019 local log = hs.logger.new('main', 'info') DEVELOPING_THIS = false -- set to true to ease debugging HYPER = {'ctrl', 'shift', 'alt', 'cmd'} -- App bindings function setUpAppBindings() hyperFocusAll('w', 'React Native Debugger', 'Simulator', 'qemu-system-x86_64') hyperFocusOrOpen('e', 'Notes') hyperFocus('i', 'IntelliJ IDEA', 'IntelliJ IDEA-EAP', 'Xcode', 'Android Studio', 'Atom', 'Code') hyperFocusOrOpen('a', 'Finder') hyperFocusOrOpen('x', 'Calendar') hyperFocusOrOpen('m', 'Messages') hyperFocusOrOpen('r', 'Slack') hyperFocus('t', 'Safari') hyperFocusOrOpen(';', 'iTerm2') hyperFocusOrOpen('s', 'OmniFocus') hyperFocus('f', 'Google Chrome', 'Firefox') hyperFocusOrOpen('space', 'Sublime Text') end -- Window management function setUpWindowManagement() hs.window.animationDuration = 0 -- disable animations hs.grid.setMargins({0, 0}) hs.grid.setGrid('2x2') function mkSetFocus(to) return function() hs.grid.set(hs.window.focusedWindow(), to) end end local fullScreen = hs.geometry("0,0 2x2") local leftHalf = hs.geometry("0,0 1x2") local rightHalf = hs.geometry("1,0 1x2") local upperLeft = hs.geometry("0,0 1x1") local lowerLeft = hs.geometry("0,1 1x1") local upperRight = hs.geometry("1,0 1x1") local lowerRight = hs.geometry("1,1 1x1") hs.hotkey.bind(HYPER, 'l', mkSetFocus(fullScreen)) hs.hotkey.bind(HYPER, 'h', mkSetFocus(leftHalf)) hs.hotkey.bind(HYPER, "'", mkSetFocus(rightHalf)) hs.hotkey.bind(HYPER, "y", mkSetFocus(upperLeft)) hs.hotkey.bind(HYPER, "b", mkSetFocus(lowerLeft)) hs.hotkey.bind(HYPER, "u", mkSetFocus(upperRight)) hs.hotkey.bind(HYPER, "n", mkSetFocus(lowerRight)) hs.hotkey.bind(HYPER, "up", hs.window.filter.focusNorth) hs.hotkey.bind(HYPER, "down", hs.window.filter.focusSouth) hs.hotkey.bind(HYPER, "left", hs.window.filter.focusWest) hs.hotkey.bind(HYPER, "right", hs.window.filter.focusEast) -- hs.hotkey.bind(HYPER, "v", hs.window.filter.focusNorth) -- hs.hotkey.bind(HYPER, "c", hs.window.filter.focusSouth) -- hs.hotkey.bind(HYPER, "j", hs.window.filter.focusWest) -- hs.hotkey.bind(HYPER, "p", hs.window.filter.focusEast) hs.hotkey.bind(HYPER, "q", hs.hints.windowHints) -- HYPER "d" -- Bound in Karabiner to Cmd+Tab (application switcher) -- HYPER "k" -- Bound in Karabiner to Cmd+` (next window of application) -- throw to other screen hs.hotkey.bind(HYPER, 'o', function() local window = hs.window.focusedWindow() window:moveToScreen(window:screen():next()) end) end -- focus on the last-focused window of the application given by name, or else launch it function hyperFocusOrOpen(key, app) local focus = mkFocusByPreferredApplicationTitle(true, app) function focusOrOpen() return (focus() or hs.application.launchOrFocus(app)) end hs.hotkey.bind(HYPER, key, focusOrOpen) end -- focus on the last-focused window of the first application given by name function hyperFocus(key, ...) hs.hotkey.bind(HYPER, key, mkFocusByPreferredApplicationTitle(true, ...)) end -- focus on the last-focused window of every application given by name function hyperFocusAll(key, ...) hs.hotkey.bind(HYPER, key, mkFocusByPreferredApplicationTitle(false, ...)) end -- creates callback function to select application windows by application name function mkFocusByPreferredApplicationTitle(stopOnFirst, ...) local arguments = {...} -- create table to close over variadic args return function() local nowFocused = hs.window.focusedWindow() local appFound = false for _, app in ipairs(arguments) do if stopOnFirst and appFound then break end log:d('Searching for app ', app) local application = hs.application.get(app) if application ~= nil then log:d('Found app', application) local window = application:mainWindow() if window ~= nil then log:d('Found main window', window) if window == nowFocused then log:d('Already focused, moving on', application) else window:focus() appFound = true end end end end return appFound end end function maybeEnableDebug() if DEVELOPING_THIS then log.setLogLevel('debug') log.d('Loading in development mode') -- automatically reload changes when we're developing hs.pathwatcher.new(os.getenv('HOME') .. '/.hammerspoon/', hs.reload):start() hs.alert('Hammerspoon config reloaded') log:d('Hammerspoon config reloaded') end end function setUpClipboardTool() ClipboardTool = hs.loadSpoon('ClipboardTool') ClipboardTool.show_in_menubar = false ClipboardTool:start() ClipboardTool:bindHotkeys({ toggle_clipboard = {HYPER, "p"} }) end -- Main maybeEnableDebug() setUpAppBindings() setUpWindowManagement() setUpClipboardTool()

Then run Hammerspoon (or “Reload Config” from the menu bar icon).

The configuration file is pretty easy to read with a little effort. If you set up the Hyper key, then this file should just work for you, and I find these particular settings to be very useful.

3: Tap shift to move over words

I find that moving my cursor word-by-word is very useful even outside my text editor. OS X provides line-editing shortcuts similar to Readline/Emacs, for example Control+a to jump to the beginning of a line and Control+e to jump to the end (which I recommend you use). Even better is to do this without a modifier key!

Inspired by a similar idea about parens from Steve Losh, notice that tapping your shift key normally does nothing, and you rarely do so. Instead, why not tap left-shift to move one word to the left, and tap right-shift to move one word to the right?

I find this very useful for moving short distances in text of all kinds, and it soon becomes second nature.

Setup

First install Karabiner Elements.

Then, install this karabiner configuration.

Alternatively, you can download this file and add it by hand to the complex modifications section of ~/.config/karabiner/karabiner.json .

4: Right-thumb control key

How often do you use the right-side command key? Never.

Instead of wasting that key, why not turn it into a control key? Control is very useful if you spend time in the terminal (iTerm2 is great) so you can do e.g. control+p to get the previous command, or control+r to search previous commands.

You press this key by curling your right thumb. It may feel unnatural at first, but before long the relief on your left pinky will be palpable.

Setup

First install the excellent Karabiner Elements.

Then open it and under “Simple Modifications” change the “From key” right_command and the “To key” to left_control . Done!

5: Ctrl+W deletes the previous word

When you make a typing mistake, it is often faster to rewrite the entire last word than to repeatedly press backspace to erase the typo. I find this to be much more efficient overall. Emacs users will already be familiar with the choice of Ctrl+W.

Setup

First install Karabiner Elements.

Then, install this karabiner configuration.

Alternatively, you can download this file and add it by hand to the complex modifications section of ~/.config/karabiner/karabiner.json .

This works especially well with the right thumb control key.

6: A better shell with oh-my-zsh

Most people use Bash for their shell. Assuming you spend some time in the terminal, you probably already know your way around Bash pretty well. Sadly, Bash has limited capabilities, especially for command completion. Other shells, like Zsh, offer more but can be unfamiliar.

Enter oh-my-zsh: a layer of frosting on top of the powerful Z-shell that makes it Bash-compatible and adds a self-updating system of plugins and themes. You will find your autocomplete much improved as well as available niceties like changing directories by typing directory names without cd , and a very nice default prompt.

Oh-my-zsh also opens the door to more powerful customizations, and boasts an active community of people who have often already implemented features you wish your shell had.

Setup

First install Zsh. If you use Homebrew—which I hope you do—then simply brew install zsh . You will also need Git ( brew install git ). Then just run the curl-to-shell command from oh-my-zsh’s setup instructions, open up ~/.zshrc and enable a few plugins by adding them to the plugins=( ... ) array you’ll see in that file, and you’re done! Be sure to open a new terminal window to see your changes.

7: Syntax highlighting in the terminal

I love syntax highlighting because it lets me catch errors in my code as I type. Why not have the same thing for my bash commands in the terminal?

This syntax highlighting makes valid commands yellow, invalid commands red, highlights strings, and underlines valid file paths.

Setup

This one requires oh-my-zsh as seen in the previous tip. Syntax highlighting is an oh-my-zsh plugin, but it doesn’t come bundled by default.

Run the commands

mkdir ~/.oh-my-zsh/custom/plugins git clone git://github.com/zsh-users/zsh-syntax-highlighting.git ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting

to install the plugin. Then open up ~/.zshrc and add zsh-syntax-highlighting to the end of the plugins=( ... ) array. Open up a new terminal and enjoy your syntax highlighting!

8: Faster key repeat

OS X defaults to a very slow key repeat rate, but it also doesn’t let you lower it enough to please me in System Preferences. This often comes in handy for moving short distances in text.

Setup

You can customize this by running commands in your Terminal.

Here are the settings I use, that I got from somewhere online (fractional values do not work). You may need to log out and back in to see them applied.

defaults write -g InitialKeyRepeat -int 10 # normal minimum is 15 (225 ms) defaults write -g KeyRepeat -int 1 # normal minimum is 2 (30 ms)

9: File-searching aliases

Many terminal users set up aliases to shorten common tasks, such as alias gc='git commit' so that the whole command doesn’t need to be typed. You should of course set up custom aliases, but there are two in particular that I find useful quite often.

The first is f , which searches the current directory subtree for files with names containing a string (ignoring case). f png would find all PNG files in the current subtree, as well as “PNGisMyFavorite.txt” and so forth.

The second is r , which recursively greps the current directory subtree for files matching a pattern. r HTTP would grep for files containing that exact string, while r '"http[^"]*"' -i would search for double-quoted strings starting with “http”, ignoring case.

Setup

We will actually implement these as Bash (or Zsh) functions or aliases in ~/.bashrc or ~/.zshrc . Just add these lines anywhere in the file appropriate to your shell:

400: Invalid request

Software

Some of these improvements require software. Here are some of the most important.

Karabiner lets you rebind keys, key combinations, trackpad gestures, set key delays, and more. It allows customization through a GUI, or a configuration file which lives at ~/.config/karabiner/karabiner.json .

Hammerspoon is a macOS swiss army knife. I mostly use it for window management, not unlike other tools like Divvy or SizeUp. It lets you arrange, resize, switch between, and open applications and windows on one or more monitors, among many other things. It can be customized through a Lua file at ~/.hammerspoon/init.lua .

Oh My Zsh makes it easy to get a great shell configuration, making your work in the terminal much better. It is community maintained and has many plugins.

More resources

I tried to hit high-value improvements that I don’t think most people already have, but there is a whole world of deeper customization out there. Here are some links to resources I’ve found useful: