| 📦 Turbo Python 6fc62ea k33g 10h ago | 1 | # Reference: languages coloured |
| 2 | |
| 3 | > Neutral description of which files Turbo Python colours, how it decides, and what each scanner recognises. |
| 4 | |
| 5 | ## Recognition |
| 6 | |
| 7 | A file's **extension** decides whenever it is one of these: |
| 8 | |
| 9 | | Extension | Language | |
| 10 | | --- | --- | |
| 11 | | `.py`, `.pyi`, `.pyw` | Python | |
| 12 | | `.toml` | TOML | |
| 13 | | `.yaml`, `.yml` | YAML | |
| 14 | | `.md`, `.markdown` | Markdown | |
| 15 | | `.js`, `.mjs`, `.cjs` | JavaScript | |
| 16 | | `.html`, `.htm` | HTML | |
| 17 | | `.xml`, `.xsd`, `.xsl`, `.xslt`, `.svg`, `.plist`, `.csproj`, `.pom` | XML | |
| 18 | | `.sh`, `.bash`, `.zsh` | Shell | |
| 19 | | `.dockerfile`, `.containerfile` | Dockerfile | |
| 20 | |
| 21 | Extensions are matched case-insensitively, and only the last one counts: `main.py.backup` is not Python. |
| 22 | |
| 23 | A file whose extension decides nothing is looked up by **name** next. Only files that carry no useful extension need this: |
| 24 | |
| 25 | | Name | Language | |
| 26 | | --- | --- | |
| 27 | | `Dockerfile`, `Containerfile` | Dockerfile | |
| 28 | |
| 29 | A name matches on the whole of it or on the part before the first dot, ignoring case — so `Dockerfile`, `dockerfile` and `Dockerfile.dev` are all recognised, while `Dockerfile.md` is Markdown, because the extension is consulted first. |
| 30 | |
| 31 | A file that neither table claims is read by its **first line**. A shebang naming `python` or `python3` makes it Python, and one naming a shell — `sh`, `bash`, `zsh`, `dash` or `ksh` — makes it a shell script. The interpreter is recognised as a path element or as the argument to `env`. That is what colours a script in a `bin` directory, a git hook, or `configure`. |
| 32 | |
| 33 | | First line | Result | |
| 34 | | --- | --- | |
| 35 | | `#!/bin/sh` | Shell | |
| 36 | | `#!/usr/bin/env bash` | Shell | |
| 37 | | `#!/usr/bin/env -S bash -e` | Shell | |
| 38 | | `#!/usr/bin/env python3` | Python | |
| 39 | | `#!/usr/bin/python` | Python | |
| 40 | | `#!/usr/bin/env node` | Not coloured | |
| 41 | | Anything not starting `#!` | Not coloured | |
| 42 | |
| 43 | The order is fixed — extension, then name, then first line — and the first to decide wins: a `.md` file starting with a Python shebang is Markdown. |
| 44 | |
| 45 | Everything else is shown in plain text. That is not an error — opening a PNG in the editor is not a mistake, it is just not coloured. |
| 46 | |
| 47 | ## Classes |
| 48 | |
| 49 | Every scanner produces the same vocabulary of classes, and each maps to one theme key. |
| 50 | |
| 51 | | Class | Theme key | Produced by | |
| 52 | | --- | --- | --- | |
| 53 | | `identifier` | `syntax.identifier` | Python, TOML, JavaScript, shell, YAML, Dockerfile | |
| 54 | | `keyword` | `syntax.keyword` | Python, JavaScript, shell, HTML (doctype), XML, Dockerfile | |
| 55 | | `type` | `syntax.type` | Python, TOML (table headers), YAML (tags) | |
| 56 | | `builtin` | `syntax.builtin` | Python (builtins, `self`, dunders), JavaScript, shell (builtins and expansions), YAML (anchors and aliases), Dockerfile (variables) | |
| 57 | | `constant` | `syntax.constant` | Python, TOML, JavaScript, shell, YAML, HTML and XML (entities) | |
| 58 | | `function` | `syntax.function` | Python, JavaScript, shell (the command) | |
| 59 | | `string` | `syntax.string` | all | |
| 60 | | `char` | `syntax.char` | nothing here; the class exists for languages that have a character type, and Python has none | |
| 61 | | `number` | `syntax.number` | Python, TOML, JavaScript, shell, YAML, Dockerfile | |
| 62 | | `comment` | `syntax.comment` | Python, TOML, JavaScript, shell, HTML, YAML, XML, Dockerfile | |
| 63 | | `operator` | `syntax.operator` | Python, TOML, JavaScript, shell, HTML, YAML (block scalar headers), XML, Dockerfile | |
| 64 | | `punctuation` | `syntax.punctuation` | Python, TOML, JavaScript, shell, Markdown, YAML, Dockerfile | |
| 65 | | `heading` | `syntax.heading` | Markdown | |
| 66 | | `tag` | `syntax.tag` | HTML, XML | |
| 67 | | `attribute` | `syntax.attribute` | Python (decorators), HTML, XML, Dockerfile (flags) | |
| 68 | | `emphasis` | `syntax.emphasis` | Markdown | |
| 69 | | `link` | `syntax.link` | Markdown | |
| 70 | |
| 71 | ## Python |
| 72 | |
| 73 | Hand-written, in `internal/pythonlang`. **Only a string crosses a line break**, and it does so in two ways: a triple-quoted one runs until the matching three quotes, and a single-quoted one runs on only when the line ends with a backslash. Which quote opened it is carried, because a literal opened with three double quotes and one opened with three apostrophes are different strings. |
| 74 | |
| 75 | | Recognised | As | |
| 76 | | --- | --- | |
| 77 | | `and`, `as`, `assert`, `async`, `await`, `break`, `class`, `continue`, `def`, `del`, `elif`, `else`, `except`, `finally`, `for`, `from`, `global`, `if`, `import`, `in`, `is`, `lambda`, `nonlocal`, `not`, `or`, `pass`, `raise`, `return`, `try`, `while`, `with`, `yield` | keyword | |
| 78 | | `match` and `case`, when they open the line and the line ends with `:` | keyword | |
| 79 | | `True`, `False`, `None`, `NotImplemented`, `Ellipsis`, `__debug__` | constant | |
| 80 | | `bool`, `bytearray`, `bytes`, `complex`, `dict`, `float`, `frozenset`, `int`, `list`, `memoryview`, `object`, `range`, `set`, `slice`, `str`, `tuple`, `type` | type | |
| 81 | | any other name starting with a capital — `ValueError`, `Measurement` | type | |
| 82 | | a name written wholly in capitals — `MAX_SIZE`, `PI`, `HTTP_PORT` | constant | |
| 83 | | `__init__`, `__repr__`, `__name__` and every other dunder | builtin | |
| 84 | | `print`, `len`, `open`, `sorted`, `isinstance`, … and `self`, `cls` | builtin | |
| 85 | | any other name immediately before `(` | function | |
| 86 | | `"…"` and `'…'`, with any prefix: `r`, `b`, `u`, `f`, `rb`, `br`, `fr`, `rf`, in either case | string | |
| 87 | | `"""…"""` and `'''…'''`, across as many lines as they take | string | |
| 88 | | a single-quoted string whose line ends with a backslash, onto the next line | string | |
| 89 | | `42`, `1_000`, `0xFF`, `0o17`, `0b1010`, `.5`, `1.`, `1.5e-3`, `1E+7`, `3j` | number | |
| 90 | | `#` to the end of the line, the shebang included | comment | |
| 91 | | `@property`, `@app.route`, `@pytest.mark.parametrize` — the name only | attribute | |
| 92 | | `:=` | operator | |
| 93 | | `:` everywhere else — a block, a slice, a dict, an annotation | punctuation | |
| 94 | | `@` anywhere but the start of a line | operator | |
| 95 | | a `\` ending a line | punctuation | |
| 96 | | runs of `+-*/%=<>!&\|^~?` | operator | |
| 97 | | `()[]{},;.` | punctuation | |
| 98 | |
| 99 | **`match` and `case` are keywords only where a match statement puts them.** They are reserved in no context at all — `match = re.match(pattern, text)` is ordinary Python — so the shape of the statement decides: the word opens the line, and the line ends with the colon that opens its block. Both conditions have to hold. |
| 100 | |
| 101 | **A name written wholly in capitals is a constant, and any other capitalised name is a type.** PEP 8 separates the two conventions clearly enough to read them: `MAX_SIZE` is a constant and `Measurement` is a class. Turbo Rust has only the second rule and documents `SCREAMING_SNAKE_CASE` as a known wrong answer; here that answer is worth removing rather than inheriting. |
| 102 | |
| 103 | **A capitalised name is a type even when it is called.** `ValueError("nope")` and `parse("nope")` are the same shape, because a class is called exactly the way a function is — so the parenthesis cannot tell them apart and the convention has to. This is the one rule Turbo Python and Turbo Rust order differently. |
| 104 | |
| 105 | **`self` and `cls` are coloured as builtins although the language does not name them.** They are a convention: a method may call its first parameter anything. But every Python reader reads `self` as the language's, the way a Rust reader reads `Some`, and every other highlighter agrees. The cost is a parameter honestly named `self` in a plain function being coloured too. |
| 106 | |
| 107 | **A decorator stops at its arguments.** `@pytest.mark.parametrize("n", [1, 2])` colours the dotted name as an attribute and the rest as ordinary Python, so the string and the list inside it keep their own colours. |
| 108 | |
| 109 | **A backslash takes the rune after it out of consideration in a raw string too.** `r"\""` is one complete string: in a raw string the backslash stays in the value, but it still stops the quote after it from ending the literal. Termination is the same rule for both, which is why rawness is not carried between lines. |
| 110 | |
| 111 | **A string that reaches the end of a line without either of the two reasons to carry on is dropped there.** It is coloured to the end of that line and the next line is code again — because a single-quoted string with no closing quote is source in the middle of being typed, and carrying it would paint the rest of the file. |
| 112 | |
| 113 | **Not recognised**, each for a stated reason: |
| 114 | |
| 115 | | Not recognised | Because | |
| 116 | | --- | --- | |
| 117 | | The `{expression}` inside an f-string | Since Python 3.12 it may hold anything — nested quotes, comments, another f-string. One flat run of string is the honest answer; colouring it half-properly ends `f"{n:{width}}"` at the inner brace | |
| 118 | | `match` or `case` on a line with a trailing comment | The colon is found by reading backwards from the end of the line, and a comment hides it, so `match value: # dispatch` colours `match` as a name. That is the safe direction to be wrong in | |
| 119 | | A docstring as anything but a string | It *is* a string — `help()` reads it back as one — and colouring it as a comment would be wrong the moment one is assigned to a name | |
| 120 | | A class whose name is all capitals | `HTTP` is coloured as a constant. That is the price of `MAX_SIZE` being coloured correctly, and the trade goes the way the commoner case does | |
| 121 | | `type` as the soft keyword of `type Alias = int` | It is also a builtin type, and reading as the type is right in both of its jobs | |
| 122 | | Whether a name is bound in this scope | Nothing here reads more than one line at a time; that is the language server's question, and [F1 answers it](../how-to/ask-about-code.md) | |
| 123 | |
| 124 | ## TOML |
| 125 | |
| 126 | | Recognised | As | |
| 127 | | --- | --- | |
| 128 | | `# comment` | comment | |
| 129 | | `[table]`, `[[array]]` | the name as a type, the brackets as punctuation | |
| 130 | | `key =` | identifier, then operator | |
| 131 | | `"basic"`, `'literal'`, `"""multi-line"""`, `'''multi-line'''` | string | |
| 132 | | `true`, `false` | constant | |
| 133 | | numbers, dates, times, `inf`, `nan` | number | |
| 134 | |
| 135 | ## YAML |
| 136 | |
| 137 | A compose file, a Kubernetes manifest and a CI workflow are all this: there is no separate dialect, because a dialect would be somebody else's schema to keep in step with. |
| 138 | |
| 139 | | Recognised | As | |
| 140 | | --- | --- | |
| 141 | | `# comment` | comment | |
| 142 | | `key:` before a space or the end of the line | the key as an identifier, the colon as punctuation | |
| 143 | | `"quoted": 1`, `'quoted': 1` | the quoted key as an identifier | |
| 144 | | `- ` opening a sequence entry | punctuation | |
| 145 | | `"…"`, `'…'` | string | |
| 146 | | `true`, `false`, `yes`, `no`, `on`, `off`, `null` | constant, whatever their case | |
| 147 | | numbers, dates and times written without quotes | number | |
| 148 | | `&anchor`, `*alias` | builtin | |
| 149 | | `!!str`, `!Custom` | type | |
| 150 | | `---`, `...` | the whole line as punctuation | |
| 151 | | `{`, `}`, `[`, `]`, `,` | punctuation | |
| 152 | | `\|`, `>`, with their chomping and indentation indicators | the header as an operator, the body as a string | |
| 153 | |
| 154 | **A colon is a separator only when a space or the end of the line follows it.** `image: nginx:1.27` is a key and one value, and `url: http://example.com/x` is a key and one URL — colouring the inner colons as separators would put every image tag and every URL in three colours. |
| 155 | |
| 156 | **A block scalar's extent is decided by indentation**, not by a delimiter. The first content line after `|` or `>` fixes the block's indentation; every line indented at least that far belongs to it, and the first line that is not ends it. **A blank line inside a block stays inside it**: a literal scalar keeps its empty lines, and ending the block at the first paragraph break would cut a shell script in a CI file in half. |
| 157 | |
| 158 | **A `#` needs a space before it to start a comment**, so `colour: ff#00aa` is one scalar. |
| 159 | |
| 160 | | Not recognised | Because | |
| 161 | | --- | --- | |
| 162 | | The schema of a compose file, a manifest or a workflow | Colouring `services:` differently from any other key means carrying somebody else's schema, and it goes stale the day they add a key | |
| 163 | | Multi-document streams as separate documents | `---` is coloured, but nothing is reset at it; nothing in the colouring depends on document boundaries | |
| 164 | | Whether a bare word is a string or a number to a parser | `1.2.3` is a version to a reader and a string to YAML; the scanner colours what it looks like | |
| 165 | |
| 166 | ## Markdown |
| 167 | |
| 168 | | Recognised | As | |
| 169 | | --- | --- | |
| 170 | | `# Heading` … `###### Heading` | the whole line as a heading | |
| 171 | | `**bold**`, `__bold__`, `*italic*`, `_italic_` | emphasis | |
| 172 | | `` `code` `` | string | |
| 173 | | `[text](target)`, `` | the whole thing as a link | |
| 174 | | `- `, `* `, `+ `, `1. `, `1) ` | the marker as punctuation | |
| 175 | | `>` | punctuation | |
| 176 | | `---`, `***`, `___` | punctuation | |
| 177 | | ` ``` ` and `~~~` fences | the whole block, opening and closing lines included, as a string | |
| 178 | |
| 179 | A fenced block is **one colour whatever language it announces**: ```` ```python ```` does not colour its contents as Python. The run of markers that opens a block must be matched by the same character to close it, so a backtick fence is not closed by a tilde one. An unclosed fence colours to the end of the file. |
| 180 | |
| 181 | The run of markers opening emphasis must be matched by a run of the same length, so `**bold**` is one span rather than two italics. |
| 182 | |
| 183 | ## JavaScript |
| 184 | |
| 185 | | Recognised | As | |
| 186 | | --- | --- | |
| 187 | | `const`, `let`, `function`, `class`, `async`, `await`, `import`, `export`, … | keyword | |
| 188 | | `true`, `false`, `null`, `undefined`, `NaN`, `Infinity`, `this` | constant | |
| 189 | | `console`, `document`, `window`, `Array`, `Object`, `Promise`, `Math`, `JSON`, … | builtin | |
| 190 | | a name immediately before `(` | function | |
| 191 | | `"…"`, `'…'` | string | |
| 192 | | `` `…` ``, interpolations included, across lines | string | |
| 193 | | `//` to end of line, `/* … */` across lines | comment | |
| 194 | | `42`, `3.14`, `0x1f`, `0b1010`, `0o777`, `1_000_000`, `1e6`, `10n` | number | |
| 195 | | runs of `+-*/%=<>!&|^~?:` | operator | |
| 196 | | `()[]{},;.` | punctuation | |
| 197 | |
| 198 | **Regular-expression literals are not recognised.** Telling `/x/g` from a division needs to know whether the previous token could end an expression; a wrong guess colours the rest of a line as a string, which is worse than leaving a regex the colour of an operator. |
| 199 | |
| 200 | Globals are recognised by name, so a file that shadows `Math` still has it coloured as a builtin — the same rule Python's builtins follow here. |
| 201 | |
| 202 | ## HTML |
| 203 | |
| 204 | | Recognised | As | |
| 205 | | --- | --- | |
| 206 | | `<tag`, `</tag`, `>`, `/>` | tag | |
| 207 | | attribute names, including `data-*`, `xlink:href`, `@click`, `v-bind.prop` | attribute | |
| 208 | | `=` | operator | |
| 209 | | `"…"`, `'…'` | string | |
| 210 | | `<!-- … -->`, across lines | comment | |
| 211 | | `&`, `©` | constant | |
| 212 | | `<!DOCTYPE …>` and other declarations | keyword | |
| 213 | |
| 214 | Text between tags is not coloured. A bare `&` with no `;` within 32 characters is left alone, because it is legal text. |
| 215 | |
| 216 | **The contents of `<script>` and `<style>` are not coloured** as JavaScript and CSS. |
| 217 | |
| 218 | ## XML |
| 219 | |
| 220 | Its own scanner rather than HTML's, for one reason that matters: CDATA. The whole point of `<![CDATA[ … ]]>` is that its contents are *not* markup, and colouring the tags inside one as tags is exactly backwards. |
| 221 | |
| 222 | | Recognised | As | |
| 223 | | --- | --- | |
| 224 | | `<?xml version="1.0"?>` and other processing instructions | the target and `?>` as keyword, the pairs between as attributes and strings | |
| 225 | | `<!DOCTYPE …>` and the other `<!` forms | keyword | |
| 226 | | `<!-- … -->`, across lines | comment | |
| 227 | | `<![CDATA[ … ]]>`, across lines | string | |
| 228 | | `<tag`, `</tag`, `>`, `/>` | tag | |
| 229 | | `<ns:tag>`, `xsi:type` | the prefix and the local name as **one** span | |
| 230 | | attribute names | attribute | |
| 231 | | `=` | operator | |
| 232 | | `"…"`, `'…'` | string | |
| 233 | | `&`, `©` | constant | |
| 234 | |
| 235 | **A comment and a CDATA section close on different delimiters**, and are carried separately: a `-->` inside a CDATA section does not end it. |
| 236 | |
| 237 | **A bare `&` with no semicolon within 32 characters is left alone**, because it is legal text in plenty of documents and swallowing the rest of the line would be the bigger mistake. |
| 238 | |
| 239 | Text between tags is not coloured. |
| 240 | |
| 241 | ## Shell |
| 242 | |
| 243 | Applies to `sh`, `bash` and `zsh` alike: the keywords recognised are the ones they share. |
| 244 | |
| 245 | | Recognised | As | |
| 246 | | --- | --- | |
| 247 | | `if`, `then`, `fi`, `for`, `while`, `case`, `esac`, `function`, `return`, … | keyword | |
| 248 | | `true`, `false` | constant | |
| 249 | | `echo`, `printf`, `export`, `local`, `read`, `cd`, `set`, `source`, … | builtin | |
| 250 | | `$NAME`, `${…}`, `$(…)`, `$1`, `$?`, `$@` | builtin | |
| 251 | | the **first bare word on a line** | function | |
| 252 | | every later bare word, and `NAME` in `NAME=value` | identifier | |
| 253 | | `'…'`, with nothing escaped or expanded inside | string | |
| 254 | | `"…"`, with the expansions inside it coloured as expansions | string | |
| 255 | | `#` to end of line | comment | |
| 256 | |
| 257 | `$(a $(b) c)` is one span: nesting is counted. An option such as `-euo` is one word, not a minus and a word. |
| 258 | |
| 259 | **Heredocs are not recognised.** `<<EOF` and the text after it are coloured as ordinary shell. |
| 260 | |
| 261 | ## Dockerfile |
| 262 | |
| 263 | | Recognised | As | |
| 264 | | --- | --- | |
| 265 | | `FROM`, `RUN`, `COPY`, `ADD`, `ARG`, `ENV`, `CMD`, `ENTRYPOINT`, `EXPOSE`, `LABEL`, `USER`, `VOLUME`, `WORKDIR`, `HEALTHCHECK`, `ONBUILD`, `SHELL`, `STOPSIGNAL`, `MAINTAINER` | keyword, in any case | |
| 266 | | `AS`, `NONE` | keyword | |
| 267 | | `# comment`, including the `# syntax=` and `# escape=` directives | comment | |
| 268 | | `--from=builder`, `--chown=me:me` | the flag name as an attribute | |
| 269 | | `$NAME`, `${NAME}`, `${NAME:-default}` | builtin, as one span to the closing brace | |
| 270 | | `"…"`, `'…'` | string | |
| 271 | | a trailing `\` | operator | |
| 272 | | numbers | number | |
| 273 | | paths and image references — `/usr/local/bin`, `golang:1.26-alpine` | identifier, as **one** span | |
| 274 | |
| 275 | **Only the first word of a line can be an instruction**, and a word that is not one is an argument — which is what keeps a continuation line's first word out of the keyword colour. |
| 276 | |
| 277 | **Nothing crosses a line break.** A `\` joins two lines for Docker, but each half still reads as a command and is coloured on its own. |
| 278 | |
| 279 | | Not recognised | Because | |
| 280 | | --- | --- | |
| 281 | | The shell inside a `RUN` | It would mean running the shell scanner over part of a line and mapping its columns out, and `RUN` may hold any language | |
| 282 | | Heredocs in a `RUN` | The same reason the shell scanner does not recognise them | |
| 283 | | Which stage a `--from` names | Nothing here reads the rest of the file | |
| 284 | |
| 285 | ## See also |
| 286 | |
| 287 | - [Theme file format](themes.md) — every key these classes resolve to |
| 288 | - [Colouring and completion](../explanation/colouring-and-completion.md) — why the scanners are written this way |
| 289 | - [How to write your own theme](../how-to/write-a-theme.md) |