Editor Support
Syntax highlighting for .stpl files. Covers ScribeEngine decorators,
{$ $} Python blocks, and Jinja2 expressions.
Neovim / Vim
Copy stpl.vim into your Vim syntax directory, then tell Vim to use it for .stpl files.
1. Copy the syntax file
# Neovim
mkdir -p ~/.config/nvim/syntax
cp stpl.vim ~/.config/nvim/syntax/stpl.vim
# Vim
mkdir -p ~/.vim/syntax
cp stpl.vim ~/.vim/syntax/stpl.vim
2. Register the filetype
Add this line to your init.vim or .vimrc:
au BufRead,BufNewFile *.stpl set filetype=stpl
Highlights: @route / @require_auth / @no_layout / @sse decorators,
{$ $} Python blocks, HTML, and Jinja2.
stpl.vim
" Quit when a syntax file was already loaded
if exists("b:current_syntax")
finish
endif
" 1. Load HTML + Jinja2 (htmldjango is built-in and covers both)
runtime! syntax/htmldjango.vim
unlet! b:current_syntax
" 2. Safely include standard Python syntax
syntax include @Python syntax/python.vim
" 3. Define your custom Python block {$ ... $}
" matchgroup colors the {$ and $} delimiters differently from the code inside
syntax region stplPythonBlock matchgroup=stplDelimiter start="{\$" end="\$}" keepend contains=@Python
" 4. Define the custom @route decorator
syntax match stplRoute "^\s*@route\s*(.*)"
" 5. Link our custom elements to standard highlight groups
hi def link stplDelimiter Delimiter
hi def link stplRoute Macro
let b:current_syntax = "stpl"
VS Code (and forks)
Install as a local extension. Works in VS Code, VS Codium, Cursor, and any VS Code fork.
Install
- Create the folder
~/.vscode/extensions/ScribeFramework.vscode-stpl/(use~/.vscodium/extensions/for VS Codium) - Copy all three files below into that folder, then move stpl.tmLanguage.json to a
syntaxes/subfolder - Restart VS Code — any
.stplfile will now have syntax highlighting
Extension layout
~/.vscode/extensions/vscode-stpl/
├── package.json
├── language-configuration.json
└── syntaxes/
└── stpl.tmLanguage.json
Highlights: @route(...) and other ScribeEngine decorators,
{$ $} Python blocks with full Python token coloring,
Jinja2 {% %} / {{ }} / {# #}, and HTML.
Files
→ vscode-stpl/package.json
{
"name": "vscode-stpl",
"displayName": "Scribe Template (.stpl)",
"description": "Syntax highlighting for ScribeEngine .stpl template files",
"publisher": "ScribeFramework",
"version": "0.1.0",
"engines": {
"vscode": "^1.80.0"
},
"categories": ["Programming Languages"],
"contributes": {
"languages": [
{
"id": "stpl",
"aliases": ["Scribe Template", "stpl"],
"extensions": [".stpl"],
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "stpl",
"scopeName": "text.html.stpl",
"path": "./syntaxes/stpl.tmLanguage.json",
"embeddedLanguages": {
"source.python": "python",
"text.html.basic": "html"
}
}
]
}
}
→ vscode-stpl/syntaxes/stpl.tmLanguage.json
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "Scribe Template",
"scopeName": "text.html.stpl",
"fileTypes": ["stpl"],
"patterns": [
{ "include": "#scribe-decorator" },
{ "include": "#python-block" },
{ "include": "#jinja-comment" },
{ "include": "#jinja-block" },
{ "include": "#jinja-variable" },
{ "include": "text.html.basic" }
],
"repository": {
"scribe-decorator": {
"comment": "ScribeEngine route decorators: @route, @require_auth, @no_layout, @sse",
"patterns": [
{
"name": "meta.function.decorator.stpl",
"match": "^(@(?:route|require_auth|no_layout|sse))\\b(\\s*\\(.*\\))?",
"captures": {
"1": { "name": "entity.name.function.decorator.stpl" },
"2": { "name": "meta.function.decorator.arguments.stpl" }
}
}
]
},
"python-block": {
"name": "meta.embedded.block.python.stpl",
"begin": "\\{\\$",
"end": "\\$\\}",
"beginCaptures": {
"0": { "name": "punctuation.definition.template-expression.begin.stpl" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.template-expression.end.stpl" }
},
"contentName": "source.python",
"patterns": [
{ "include": "source.python" }
]
},
"jinja-comment": {
"name": "comment.block.jinja.stpl",
"begin": "\\{#",
"end": "#\\}",
"beginCaptures": {
"0": { "name": "punctuation.definition.comment.begin.jinja" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.comment.end.jinja" }
}
},
"jinja-block": {
"name": "meta.embedded.block.jinja.stpl",
"begin": "\\{%-?",
"end": "-?%\\}",
"beginCaptures": {
"0": { "name": "punctuation.definition.tag.begin.jinja" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.end.jinja" }
},
"patterns": [
{ "include": "#jinja-keywords" },
{ "include": "#jinja-string" },
{ "include": "#jinja-number" },
{ "include": "#jinja-constant" }
]
},
"jinja-variable": {
"name": "meta.embedded.expression.jinja.stpl",
"begin": "\\{\\{",
"end": "\\}\\}",
"beginCaptures": {
"0": { "name": "punctuation.definition.expression.begin.jinja" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.expression.end.jinja" }
},
"patterns": [
{ "include": "#jinja-string" },
{ "include": "#jinja-number" },
{ "include": "#jinja-constant" },
{ "include": "#jinja-filter" }
]
},
"jinja-keywords": {
"name": "keyword.control.jinja",
"match": "\\b(if|elif|else|endif|for|endfor|block|endblock|extends|include|macro|endmacro|call|endcall|filter|endfilter|set|do|not|and|or|in|is|import|from|with|without|context|ignore|missing|recursive|loop|super|raw|endraw)\\b"
},
"jinja-string": {
"patterns": [
{
"name": "string.quoted.double.jinja",
"begin": "\"",
"end": "\"",
"patterns": [{ "name": "constant.character.escape.jinja", "match": "\\\\." }]
},
{
"name": "string.quoted.single.jinja",
"begin": "'",
"end": "'",
"patterns": [{ "name": "constant.character.escape.jinja", "match": "\\\\." }]
}
]
},
"jinja-number": {
"name": "constant.numeric.jinja",
"match": "\\b[0-9]+(\\.[0-9]+)?\\b"
},
"jinja-constant": {
"name": "constant.language.jinja",
"match": "\\b(true|false|none|True|False|None)\\b"
},
"jinja-filter": {
"match": "\\|\\s*([a-zA-Z_][a-zA-Z0-9_]*)",
"captures": {
"1": { "name": "support.function.jinja" }
}
}
}
}
→ vscode-stpl/language-configuration.json
{
"comments": {
"blockComment": ["{#", "#}"]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
{ "open": "{{", "close": "}}" },
{ "open": "{%", "close": "%}" },
{ "open": "{#", "close": "#}" },
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"],
["<", ">"]
],
"wordPattern": "[a-zA-Z_][a-zA-Z0-9_]*"
}