The Pug Automatic

TextMate command to toggle writing deltas

Written March 24, 2007. Tagged OS X, TextMate.

TextMate's bundle system is pretty cool. If you make modifications to bundles, the difference is written as deltas to ~/Library/Application Support/TextMate/Bundles. The original bundle is kept in its unchanged, pristine state – inside the TextMate application, in ~/Library/Application Support/TextMate/Pristine Copy/Bundles if downloaded and installed, or in /Library/Application Support/TextMate/Bundles if you did a SVN checkout. (See this post for a fuller explanation of where bundles go and why.)

Since your local changes are kept apart from the distributed bundle, updates by the bundle author do not overwrite your own changes, and all is well.

But what if you are the bundle author?

Developing bundles

If you distribute the bundle as a web download, you can just drag-and-drop it from the Bundle Editor to the desktop, zip it up and share. The bundle you share will include any local changes.

If you version control a bundle you're developing, though, you probably don't want deltas, but to have any changes made in the Bundle Editor written directly to the bundle itself. This is achieved by keeping the bundle in ~/Library/Application Support/TextMate/Bundles (which is, incidentally, where bundles go when you do "New Bundle" in the Bundle Editor). No deltas are created; all changes are written directly to the bundle.

But sometimes you do want deltas. For my Greasemonkey bundle, I want TextMate-global keyboard shortcuts for some stuff (creating a new userscript from template, opening an installed userscript), but bundles should not be distributed with TextMate-global keyboard shortcuts unless it's necessary (e.g. the Subversion bundle).

To be able to easily switch between writing changes as deltas and writing changes to the bundle proper would be quite useful. So I wrote a TextMate command for this.

The command

Download: MyBundle Toggle Deltas.tmCommand.

The command assumes you keep the bundle in ~/Library/Application Support/TextMate/Bundles. When run, the command switches the deltas and the complete bundle between that directory and Pristine Copy/Bundles. When the deltas are in the non-pristine directory, any changes you make become deltas, and local changes (e.g. my global keyboard shortcuts) are available to use and modify. When the deltas are in the pristine directory, they are ignored. Changes are written directly to the bundle, and your personal, local changes are not available.

The command requires you have Growl installed, with growlnotify – it informs you of the current editing mode with a Growl notification. The notification is sticky, meaning it doesn't go away until you click it. The idea is to keep you aware of the current mode.

You need to edit the command to specify the name of the bundle in question. I only maintain one bundle, so the command only handles one. You can simply create several copies of the command, one for each bundle.

The code:

#!/usr/bin/env ruby
require "#{ENV['TM_SUPPORT_PATH']}/lib/escape"

BUNDLE = "MyBundle"

GROWLNOTIFY = "/usr/local/bin/growlnotify"
TM_SUPPORT_DIR = "#{ENV['HOME']}/Library/Application Support/TextMate"
DIRTY = "#{TM_SUPPORT_DIR}/Bundles/#{BUNDLE}.tmbundle/"
PRISTINE = "#{TM_SUPPORT_DIR}/Pristine Copy/Bundles/#{BUNDLE}.tmbundle/"
LIMBO = "#{TM_SUPPORT_DIR}/#{BUNDLE}.tmbundle/"

def writing_deltas?
not `find #{e_sh DIRTY} -regex ".*\.tmDelta$"`.empty?
end
def reload_bundles!
`osascript -e 'tell app "TextMate" to reload bundles'`
end
def mv(from, to)
`mv #{e_sh from} #{e_sh to}` if File.exist?(from)
end

reload_bundles! # Reload before moving, so any pending changes are (I hope) written to the right place
mv(DIRTY, LIMBO); mv(PRISTINE, DIRTY); mv(LIMBO, PRISTINE)
reload_bundles!

title = "#{BUNDLE}: #{writing_deltas? ? "Deltas" : "Distribution"}"
message = if writing_deltas?
"Changes become deltas and are only available locally."
else
"Changes are written directly to the distributed bundle; local changes are unavailable."
end

`#{e_sh GROWLNOTIFY} -s --icon="tmbundle" -t #{e_sh title} -m #{e_sh message}`

Bug reports and suggestions are very welcome.

Update 2007-03-31

Switching the bundles does not reload the grammar used for currently open documents: if you've made local grammar modifications, switching into or out of those modifications won't actually change any scopes (or highlighting) in open documents. You have to open the Bundle Editor and click "Test" next to the grammar to change scopes/highlighting. This is a TextMate limitation.