Beyond Document Scopes

The way to specialize things in TextMate is via the venerable scope selector matched against the current scope.

TextMate 1 created the scope by parsing the document’s content using a language grammar. Though sometimes, more context information is needed than what can be provided by the document’s content, which is why TextMate 2 has extended the information stored in a scope.

Two examples of where there is a lot of choice (and thus bundles) are build systems and revision control.

For this reason, we introduced scopes for these two things. If you have the TextMate source checked out and press ⌃⇧P (Show Scope) with README.md open then you should see something like:

text.html.markdown
attr.rev-path.md.README.TextMate.Projects.duff.Users
attr.os-version.10.8.0
attr.scm.git
attr.project.ninja
attr.scm.branch.master
attr.scm.status.H

The relevant scopes are attr.scm.git and attr.project.ninja. By having a scope for the SCM system we have been able to make all the SCM bundles use ⌘Y as key equivalent (hint ⇧⌘Y is the key for showing “SCM Status” in the file browser). Likewise, by having attr.project.ninja we can scope the Ninja bundle’s Build command to ⌘B, even though this key is also used by Make, Xcode Build, and similar.

As for the latter key though, ⌘B is also bound to “Bold” in many markup languages so we have a conflict. The Build command “wins” because the attribute scopes are more specific than the content scopes which means that if we press ⌘B in our README.md then we will build the project rather than make a word bold.

If we select a word, as we would normally do before bolding it, and show the scope, we see that TextMate 2 has another nice new thing in the scope:

text.html.markdown
⋮
dyn.selection.continuous

The new dyn.selection scope allows us to override keys only when there is a selection, for example I have rebound the { key (no modifiers, but requires a selection) to wrap and indent the selection in a pair of braces, likewise } will unwrap and unindent the selection (a feature that goes well together with ⇧⌘B to first select the braces/block).

In the case of our Build command, I ended up scoping it like this:

attr.project.ninja - (text dyn.selection)

This means that for projects with a build.ninja file, we can press ⌘B to build except when in a text file and we have a selection, in that case, ⌘B means Bold.

TextMate knows about a handful of build systems and SCM systems, if yours is missing from the list, please send us a note (or pull request) and we’ll add it.

There are times when TextMate can’t provide the desired attribute scopes. For example I use CxxTest and created a bundle with snippets that only make sense in test files. If these can be scoped specifically to my tests, I can use simpler tab triggers, for example I can override main or cl for something which is more appropriate for test files, but test files are just regular C++ files, so what to do?

If you look in the .tm_properties of the TextMate source you will see we have these lines:

[ tests/*.{cc,mm} ]
scopeAttributes = 'attr.test.cxxtest'
TM_NINJA_TARGET = '${TM_FILEPATH/^.*?([^\/]*)\/tests\/.*$/$1\/test/}'

The key here is scopeAttributes. We use that to add our own scope attributes to test files, which by convention are always placed in a tests directory.

This way the CxxTest bundle can scope all its items to attr.test.cxxtest and not worry about (globally) eclipsing “standard” keys and tab triggers, when specializing them for test files.

You’ll also notice I set the TM_NINJA_TARGET environment variable for test files. The regular expression might look scary, but basically with a path like:

/Users/duff/Projects/TextMate/Frameworks/scm/tests/t_git.cc

It will set TM_NINJA_TARGET to scm/test. This means pressing ⌘B in a test file will build the framework’s test target (i.e. run the tests).