Adds syntax highlighting, bracket matching, and basic editor support for custom languages in Visual Studio by bundling TextMate grammar files (.tmlanguage) with a VSIX and pkgdef registration. Use when porting grammars from VS Code or Sublime Text, or when providing colorization without writing a MEF classifier.
How this skill is triggered — by the user, by Claude, or both
Slash command
/vs-extensibility-skills:adding-textmate-grammarsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
TextMate grammars provide lightweight syntax highlighting, bracket matching, and basic statement completion for custom languages — without writing a full MEF classifier. Visual Studio uses the TextMate engine internally for dozens of languages (Rust, Go, Markdown, YAML, etc.).
TextMate grammars provide lightweight syntax highlighting, bracket matching, and basic statement completion for custom languages — without writing a full MEF classifier. Visual Studio uses the TextMate engine internally for dozens of languages (Rust, Go, Markdown, YAML, etc.).
A TextMate grammar is a .tmlanguage, .plist, or .json file that defines regex-based scope rules. You bundle it in a VSIX extension, register it with a .pkgdef file, and Visual Studio picks it up for any matching file extension.
TextMate grammars are the fastest path to syntax highlighting for a new file type — they require no compiled code, work with VS's existing colorization pipeline, and can often be ported directly from VS Code or Sublime Text.
When to use this vs. alternatives:
| Approach | Best for |
|---|---|
| TextMate grammar | Quick syntax coloring for a new file type; porting an existing VS Code / Sublime grammar; no compiled code needed |
MEF IClassifier (see vs-editor-classifier skill) | Full control over classification spans; dynamic/context-dependent highlighting; custom classification types with user-configurable colors |
| Language Server Protocol | Full IntelliSense, diagnostics, go-to-definition, etc. — LSP extensions can also bundle a TextMate grammar for colorization |
Not directly supported. The new extensibility model does not provide a TextMate grammar registration API. However, TextMate grammar files are loaded by the VS editor host, so an in-process hybrid extension (or a standalone VSIX that only contains the grammar + pkgdef) works alongside VisualStudio.Extensibility extensions. If you are building an LSP-based language extension with the new model, you can pair it with a TextMate grammar VSIX for colorization.
The Toolkit and VSSDK approaches are identical — TextMate grammar registration is purely declarative (no compiled C# code needed beyond the VSIX project shell).
NuGet packages: Microsoft.VisualStudio.SDK (≥ 17.0) — only needed for the VSIX project infrastructure. No runtime API calls are required.
MyLanguageGrammar/
├── Grammars/
│ ├── mylang.tmLanguage.json ← TextMate grammar definition
│ └── mylang.tmTheme ← (optional) custom theme mapping
├── Resources/
│ └── LICENSE
├── languages.pkgdef ← registers grammar + file associations
├── source.extension.vsixmanifest
└── MyLanguageGrammar.csproj
Place your .tmLanguage.json (or .tmlanguage, .plist, .json) file in a Grammars/ folder.
Grammars/mylang.tmLanguage.json:
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "MyLang",
"scopeName": "source.mylang",
"fileTypes": ["mylang", "ml"],
"patterns": [
{ "include": "#comments" },
{ "include": "#keywords" },
{ "include": "#strings" },
{ "include": "#numbers" }
],
"repository": {
"comments": {
"patterns": [
{
"name": "comment.line.double-slash.mylang",
"match": "//.*$"
},
{
"name": "comment.block.mylang",
"begin": "/\\*",
"end": "\\*/"
}
]
},
"keywords": {
"patterns": [
{
"name": "keyword.control.mylang",
"match": "\\b(if|else|while|for|return|func|var|let|const)\\b"
},
{
"name": "keyword.other.mylang",
"match": "\\b(import|export|module)\\b"
}
]
},
"strings": {
"patterns": [
{
"name": "string.quoted.double.mylang",
"begin": "\"",
"end": "\"",
"patterns": [
{
"name": "constant.character.escape.mylang",
"match": "\\\\."
}
]
},
{
"name": "string.quoted.single.mylang",
"begin": "'",
"end": "'",
"patterns": [
{
"name": "constant.character.escape.mylang",
"match": "\\\\."
}
]
}
]
},
"numbers": {
"patterns": [
{
"name": "constant.numeric.mylang",
"match": "\\b[0-9]+(\\.[0-9]+)?\\b"
}
]
}
}
}
Use standard scope names so VS maps them to built-in classification colors automatically:
| Scope name | VS color |
|---|---|
comment.line.* | Green (comment) |
comment.block.* | Green (comment) |
keyword.control.* | Blue (keyword) |
keyword.other.* | Blue (keyword) |
string.quoted.* | Red/brown (string) |
constant.numeric.* | Light green (number) |
constant.language.* | Blue (true/false/null) |
entity.name.function.* | Yellow (method name) |
entity.name.type.* | Teal (type name) |
variable.other.* | Default text |
storage.type.* | Blue (int, string, etc.) |
support.function.* | Yellow (built-in functions) |
The .pkgdef file tells Visual Studio where to find the grammar and which file extensions to associate.
languages.pkgdef:
// Register the Grammars folder as a TextMate repository
[$RootKey$\TextMate\Repositories]
"MyLang"="$PackageFolder$\Grammars"
// Associate file extensions with a language name and icon
[$RootKey$\Languages\Language Services\MyLang]
"Package"="{e0d8864a-d944-4100-a278-5c29ae219e4d}"
// Map file extensions to the language
[$RootKey$\Languages\File Extensions\.mylang]
@="MyLang"
[$RootKey$\Languages\File Extensions\.ml]
@="MyLang"
The first entry (TextMate\Repositories) is the critical one — it points VS to the folder containing .tmlanguage / .json grammar files. VS will scan all files in that folder.
All grammar files and the pkgdef must be included in the VSIX as content:
<ItemGroup>
<!-- TextMate grammar files -->
<Content Include="Grammars\mylang.tmLanguage.json">
<IncludeInVSIX>true</IncludeInVSIX>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<!-- pkgdef registration -->
<Content Include="languages.pkgdef">
<IncludeInVSIX>true</IncludeInVSIX>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
In source.extension.vsixmanifest, add the pkgdef as a VsPackage asset:
<Assets>
<Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="File" Path="languages.pkgdef" />
</Assets>
If you want to customize how scopes map to VS classification colors, add a .tmTheme file:
Grammars/mylang.tmTheme:
<?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>name</key>
<string>MyLang Theme</string>
<key>settings</key>
<array>
<dict>
<key>scope</key>
<string>keyword.control.mylang</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#569CD6</string>
</dict>
</dict>
<dict>
<key>scope</key>
<string>entity.name.function.mylang</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#DCDCAA</string>
</dict>
</dict>
</array>
</dict>
</plist>
Place it in the Grammars/ folder alongside the grammar — VS discovers it automatically.
A language-configuration.json file tells Visual Studio how to handle editor behaviors — comment toggling, bracket matching, auto-closing pairs, and indentation — for your language. Without it, you get colorization but no smart editing behaviors.
Grammars/language-configuration.json:
{
"comments": {
"lineComment": "//",
"blockComment": ["/*", "*/"]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
]
}
| Property | Description |
|---|---|
comments.lineComment | The character(s) that toggle a line comment (Ctrl+K, Ctrl+C) |
comments.blockComment | Start/end tokens for block comments |
brackets | Bracket pairs for matching and highlighting |
autoClosingPairs | Pairs that auto-close when the opening character is typed. Use notIn to suppress inside "string" or "comment" scopes |
surroundingPairs | Pairs used when wrapping a selection |
wordPattern | Regex defining what constitutes a "word" for double-click selection and word navigation |
indentationRules | increaseIndentPattern / decreaseIndentPattern regexes for auto-indent |
onEnterRules | Rules that run when Enter is pressed (e.g. continue line comments) |
Add a GrammarMapping entry that maps your grammar's scopeName to the configuration file:
[$RootKey$\TextMate\LanguageConfiguration\GrammarMapping]
"source.mylang"="$PackageFolder$\Grammars\language-configuration.json"
This goes in the same languages.pkgdef file alongside the TextMate\Repositories entry.
In the .csproj, add the file as content:
<Content Include="Grammars\language-configuration.json">
<IncludeInVSIX>true</IncludeInVSIX>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
When building an LSP extension, TextMate provides the colorization layer while the language server provides IntelliSense, diagnostics, etc. In your LSP extension project:
Grammars/ folder with your .tmlanguage.json file.languages.pkgdef with the TextMate\Repositories registration.ContentTypeDefinition for the file extension (required by the LSP client).The LSP docs cover the TextMate + LSP pairing pattern in detail: Adding an LSP extension — TextMate grammar files.
Most VS Code language extensions contain a syntaxes/ folder with .tmLanguage.json files that are directly compatible with Visual Studio:
.tmLanguage.json file into your Grammars/ folder.scopeName and fileTypes are correct.languages.pkgdef to register extensions..tmTheme files are also compatible — copy them into Grammars/ if needed.Note: VS Code snippets, commands, and
package.jsoncontributions are NOT compatible — only the grammar and theme files work in VS.
Users can add TextMate grammars without installing a VSIX by dropping files into:
%userprofile%\.vs\Extensions\<language-name>\Syntaxes\
This is useful for personal use but not for distribution.
.mylang)..pkgdef registers the grammar repository path correctly ($PackageFolder$\Grammars). Ensure all grammar files have Build Action = Content and Include in VSIX = true..tmLanguage.json — overly broad patterns can match too much. Test the grammar in VS Code first (same engine, faster iteration).begin/while patterns (use begin/end instead). VS also ignores package.json contributions — only the .pkgdef registration matters..pkgdef must include a content type mapping for the file extension. Verify the grammar's fileTypes or scopeName matches what VS expects..tmTheme file with theme-appropriate colors, or rely on the default scope-to-classification mappings which use VS theme colors.Do NOT include
package.json, VS Code commands, or VS Code snippet files — VS ignores these; only grammar and theme files are used.
Do NOT use
begin/whilepatterns — not supported in VS. Usebegin/endinstead.
Do NOT write a full MEF
IClassifierjust for syntax coloring — TextMate grammars achieve the same result with no compiled code and are portable.
Do NOT register grammars via MEF exports — use
.pkgdeffiles pointing to a grammar repository directory.
npx claudepluginhub madskristensen/vs-agent-plugins --plugin vs-extensibility-skillsAdd custom syntax highlighting and text classification to the Visual Studio editor. Covers VisualStudio.Extensibility, VSIX Community Toolkit, and VSSDK approaches.
Guides through adding language support to CocoSearch across up to 6 paths: handler, symbol extraction, grammar, context expansion, dependency extraction, and documentation.
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.