Category Archive: TextMate

A possibly better way to customize the TextMate LaTeX plugin

In the aftermath my recent adventures with a development version of a TextMate LaTeX bundle, I wound up reinstalling the original LaTeX bundle, but in the process lost my customizations for special citations and idiosyncratic inline linguistic examples.

I could have re-edited the bundle (that’s why I posted the instructions here, after all, so I could remember what I did), but I discovered a better way, looking at the TextMate manual section on language grammars and on the TextMate wiki: create my own language, subclassing the language from the LaTeX bundle, with just my additions. The advantage of this is that if I ever update the LaTeX bundle, the additions will continue to be mixed in.

This is in fact how the LaTeX Beamer language definition works, so there’s a ready-made example to look at. It defines a couple of special beamer-related things (frame and frametitle), and then includes the regular LaTeX language. So, I am doing just that. Note, though, that if I want to include the beamer specials, I need to include the beamer class. Since I don’t see any real harm in doing so all the time, I am extending that class which itself extends LaTeX.

Here’s how to do this: Go to Bundles > Bundle Editor > Edit Languages…, open up the LaTeX bundle and select the LaTeX Beamer language. Hit the duplicate button in the lower left corner to duplicate it. Then hit the add button and add a new bundle, call it something, and then drag the beamer duplicate into it. Name your duplicate something better, and click on it to edit it. Change scopeName to text.tex.latex.custom. You probably want to get rid of the firstLineMatch line as well, and change fileTypes to ( 'tex' );. The idea is that it will pick up any file with the .tex extension, without checking to see if it is a beamer file.

Then, just put in the additions. I left the beamer additions in, since I use those too. I couldn’t get an include line to work with text.tex.latex.beamer, maybe TextMate doesn’t allow nested includes. Even including both the beamer addition and the main LaTeX language didn’t work, in either order. So, I just left the beamer additions in.

For completeness, this is what I ended up with:

{	scopeName = 'text.tex.latex.custom';
	firstLineMatch = '^\documentclass([.*])?{beamer}';
	fileTypes = ( 'tex' );
	foldingStartMarker = '\begin{.*}|%.*(fold)s*$';
	foldingStopMarker = '\end{.*}|%.*(end)s*$';
	patterns = (
		{	name = 'meta.function.mot.latex';
			contentName = 'string.quoted.mot.latex';
			comment = 'Added by Paul Hagstrom';
			begin = '((\)mot)({)';
			end = '}';
			beginCaptures = {
				1 = { name = 'support.function.mot.latex'; };
				2 = { name = 'punctuation.definition.function.latex'; };
				3 = { name = 'punctuation.definition.mot.begin.latex'; };
			};
			endCaptures = { 0 = { name = 'punctuation.definition.mot.end.latex'; }; };
			patterns = ( { include = '$base'; } );
		},
		{	name = 'meta.citation.latex';
			begin = '(?x)
					(
						(\)										# Marker
						(?:foot)?(?:full)?(?:no)?(?:short)?(?:poss)?(?:pg)?		# Function Name
						[cC]ite
						(?:al)?(?:t|p|author|year(?:par)?|title)?[ANP]*
						*?											# Optional Unabreviated
					)
					(?:([)[^]]*(]))?								# Optional
					(?:([)[^]]*(]))?								#   Arguments
					({)											# Opening Bracket
				';
			end = '}';
			captures = {
				1 = { name = 'keyword.control.cite.latex'; };
				2 = { name = 'punctuation.definition.keyword.latex'; };
				3 = { name = 'punctuation.definition.arguments.optional.begin.latex'; };
				4 = { name = 'punctuation.definition.arguments.optional.end.latex'; };
				5 = { name = 'punctuation.definition.arguments.optional.begin.latex'; };
				6 = { name = 'punctuation.definition.arguments.optional.end.latex'; };
				7 = { name = 'punctuation.definition.arguments.latex'; };
			};
			endCaptures = { 0 = { name = 'punctuation.definition.arguments.latex'; }; };
			patterns = (
				{	name = 'constant.other.reference.citation.latex';
					match = '[w:.]+';
				},
			);
		},
		{	name = 'meta.function.environment.frame.latex';
			begin = '(?:s*)((\)begin)({)(frame)(})';
			end = '((\)end)({)(frame)(})';
			captures = {
				1 = { name = 'support.function.be.latex'; };
				2 = { name = 'punctuation.definition.function.latex'; };
				3 = { name = 'punctuation.definition.arguments.begin.latex'; };
				4 = { name = 'variable.parameter.function.latex'; };
				5 = { name = 'punctuation.definition.arguments.end.latex'; };
			};
			patterns = ( { include = '$self'; } );
		},
		{	name = 'meta.function.frametitle.latex';
			match = '((\)frametitle)({)(.*)(})';
			captures = {
				1 = { name = 'support.function.frametitle.latex'; };
				2 = { name = 'punctuation.definition.function.latex'; };
				3 = { name = 'punctuation.definition.arguments.begin.latex'; };
				4 = { name = 'entity.name.function.frame.latex'; };
				5 = { name = 'punctuation.definition.arguments.end.latex'; };
			};
		},
		{	include = 'text.tex.latex'; },
	);
}