2009/11/12

TextMate, Emacs and META indent-region

[cross-posted from the Desert Moon blog.]

I haven't used GNU Emacs very much since switching to TextMate in 2005. One Emacs feature which I really miss in TextMate is indent-region. It lets you take an entire region of code, whatever its language, whatever its mix of tabs and spaces and indentation widths, and re-format it using your preferred indentation style.

But wait! Emacs has a batch mode, and you can drive it from TextMate. Many thanks to Gragusa's Things for showing the way.

The post on Gragusa's Things is specific to R code, but I'm more interested in re-formatting C and C++ code. Here's my first cut at a general TextMate Bundle to re-format code regardless of the source language:

#!/usr/local/bin/python2.6
"""
Use Emacs to re-indent regions of the current buffer.
Inspired by
http://gragusa.wordpress.com/2007/11/11/textmate-emacs-like-indentation-for-r-files/
"""
import tempfile
import os
import sys
import subprocess

# Use the same filename extension so Emacs will know which
# mode to use.
ext = os.path.splitext(os.environ["TM_FILEPATH"])[-1]
outf = tempfile.NamedTemporaryFile(suffix=ext, delete=False)
pathname = outf.name

outf.write(os.environ["TM_SELECTED_TEXT"])
outf.close()

args = [
"emacs", "-batch", pathname,
# Assume no emacs-startup.el
"--eval", "(setq indent-tabs-mode nil)",
"--eval", '(c-set-style "java")',
"--eval", "(setq c-basic-offset 4)",
"--eval", "(indent-region (point-min) (point-max) nil)",
"-f", "save-buffer"]
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
if p.returncode:
print(err)

inf = open(pathname, "r")
sys.stdout.write(inf.read())
inf.close()

os.remove(pathname)


NB:
  1. Due to the use of the delete=False keyword argument to tempfile.NamedTemporaryFile, this command bundle requires Python 2.6+.
  2. TextMate on OS X 10.5 won't, by default, have /usr/local/bin in its path; hence the pathetic shebang.


Anyway, install this as a new TextMate command bundle, assign a Key Equivalent such as ⌘-Shift-R, and enjoy.

2009/11/03

Creating an 'hg ignore' extension

[cross-posted from the Desert Moon blog.]

I often wish Mercurial had an 'hg ignore' command similar to 'bzr ignore'. Turns out it's pretty easy to add one:


#!/usr/bin/env python
"""Ignore pathnames and patterns"""

import os

def ignore(ui, repo, *pathnames):
"""Ignore the given pathnames and patterns."""
outf = open(os.path.join(repo.root, ".hgignore"), "a")
for p in pathnames:
outf.write(p + "\n")
outf.close()
return

cmdtable = {
'ignore': (ignore, [], "hg ignore pathname [pathname]"),
}


To use this, save it to a file such as ${HOME}/platform/independent/lib/hg/ignore.py. Then add the extension to your ${HOME}/.hgrc:
[extensions]
~/platform/independent/lib/hg/ignore.py