Create an “Add to TextMate” Widget Using MooTools

By  on  
TextMate

If a sword is a soldier's best friend, the best friend of a programmer is a his text editor. TextMate has been my text editor of choice and a big reason for that is the ability to add snippets for quick use later. Since I provide a lot of snippets on my blog and many of my readers use TextMate, I've created an "Add To TextMate" widget using a mixture of MooTools, AJAX, CSS, and PHP.

The CSS

.tmbundle		{ display:block; float:left; background:url('textmate-icon.png') center no-repeat #eee; border:1px solid #ccc; color:#000; font-size:14px; font-weight:bold; text-decoration:none; width:26px; height:26px; text-indent:-9999px; border:1px solid #ccc; -moz-border-radius:3px; -webkit-border-radius:3px;  }

The only CSS code needed is the "Add to TextMate" link. Style as you desire.

The MooTools JavaScript

/* randomUUID.js - Version 1.0
 * 
 * Copyright 2008, Robert Kieffer
 * 
 * This software is made available under the terms of the Open Software License
 * v3.0 (available here: http://www.opensource.org/licenses/osl-3.0.php )
 *
 * The latest version of this file can be found at:
 * http://www.broofa.com/Tools/randomUUID.js
 *
 * For more information, or to comment on this, please go to:
 * http://www.broofa.com/blog/?p=151
 */
/**
 * Create and return a "version 4" RFC-4122 UUID string.
 */

function randomUUID() {
  var s = [], itoh = '0123456789ABCDEF';
  // Make array of random hex digits. The UUID only has 32 digits in it, but we
  // allocate an extra items to make room for the '-'s we'll be inserting.
  for (var i = 0; i <36; i++) s[i] = Math.floor(Math.random()*0x10);
  // Conform to RFC-4122, section 4.4
  s[14] = 4;  // Set 4 high bits of time_high field to version
  s[19] = (s[19] & 0x3) | 0x8;  // Specify 2 high bits of clock sequence
  // Convert to hex chars
  for (var i = 0; i <36; i++) s[i] = itoh[s[i]];
  // Insert '-'s
  s[8] = s[13] = s[18] = s[23] = '-';
  return s.join('');
}

window.addEvent('domready',function() {
	
	var label;
	
	var request = new Request({
		url: 'make-textmate-snippet.php',
		method: 'post',
		onSuccess: function(url) {
			window.location = 'make-textmate-snippet.php?file=' + url + '&label=' + label;
		}
	});
	
	
	$$('pre').each(function(pre) {
		var icon = new Element('a',{
			text: 'Click here to add this snippet to TextMate',
			href: '#',
			'class': 'tmbundle',
			events: {
				click: function(e) {
					e.stop();
					var name = prompt('What would you like this snippet to be named?');
					if(name) {
						label = name;
						request.send('code=' + pre.get('html') + '&label=' + name + '&uid=' + randomUUID());
					}
				}
			}
		}).inject(pre,'after');
	});
});

For every PRE tag in the page, we attach our "Add to TextMate" link. When a user clicks the link they are prompted to give the snippet a name. An AJAX request then fires to the server and a ".tmSnippet" file is generated. The AJAX request returns a URL and the window is pushed to that URL which forces download of the file. Note that you will need to reposition the button as you wish -- I'm simply allowing the button to fall below the PRE element.

The PHP

$snippets_dir = 'tmsnippets/';

/* action */
if($_POST['code'] && $_POST['label']) {

	$file = md5($_POST['code']).'.tmSnippet';
	$xml = '<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
	<plist version="1.0">
	<dict>
		<key>bundleUUID</key>
		<string>DE517C0E-6A24-453D-9809-CCF6415DA2B9</string>
		<key>content</key>
		<string>'.$_POST['code'].'</string>
		<key>name</key>
		<string>'.$_POST['label'].'</string>
		<key>uuid</key>
		<string>'.$_POST['uid'].'</string>
	</dict>
	</plist>
	';
	file_put_contents($snippets_dir.$file,$xml);
	die($file);
}
elseif($_GET['label'] && preg_match('/^[a-z0-9]{32}\.tmSnippet$/i', $_GET['file']) && file_exists($snippets_dir.$_GET['file'])) {
	if(ini_get('zlib.output_compression')) { ini_set('zlib.output_compression', 'Off');	}
	header('Pragma: public');
	header('Expires: 0');
	header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
	header('Last-Modified: '.gmdate ('D, d M Y H:i:s', filemtime ($snippets_dir.$_GET['file'])).' GMT');
	header('Cache-Control: private',false);
	header('Content-Type: TextMate snippet');
	header('Content-Disposition: attachment; filename="'.$_GET['label'].'.tmSnippet"');
	header('Content-Transfer-Encoding: binary');
	header('Content-Length: '.filesize($snippets_dir.$_GET['file']));
	header('Connection: close');
	readfile($snippets_dir.$_GET['file']);
	exit();
}

Notice that I use the same file to do the force-download and the file creation. The initial request generates the file and the and returns the file name. The second request force-downloads the snippet to your computer

Coda Clips

Some of you probably use another great text editor named Coda. Chris Coyier is debuting an article today on how you can use the power of Coda Clips and TextMate within your blog using jQuery. Check it out!

I'll admit that my code is a bit dirty but it works well -- try it for yourself on this post!. I'm interested in getting your suggestions so we can optimize this one together. Share your ideas!

Recent Features

Incredible Demos

Discussion

  1. At the risk of sounding like I completely missed the coolness of this article, I learned a number of headers I should be setting in my PHP for the ultimate control when triggering a download.

    The rest of the concept is super cool, and I hope many sites incorporate it! Will make my life a lot easier for sure.

    Now if we can only convince TM to have a custom protocol like Coda does :)

  2. Agreed; a custom protocol would be great.

  3. Sorry if I missed something but why are you and Chris Coyier posting About the same things lately?

  4. @Spencerf

    Umm I don’t know how you’ve been reading the articles, but they not really the same, Chris Coyier posted an announcement of a new feature in the Snippets section, David Walsh posted how to do it, I’m pretty sure there is a differences

  5. I must have missed this post yesterday.
    I just stumbled upon this new feature that you have implemented in this blog and was amazed – how cool is it to be able to add your code to my Coda snippets at the click of a button!
    One more reason to keep on reading your blog :)
    Excellent work David :)

  6. Thanks David, this is really cool!

  7. Pascal Veyradier

    brilliant David. Thanks!!

  8. Yeah really nice work, I have also used some code from these comments lol. But really you can have a compression like Google with this snippet.
    Thanks alot really.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!