[JEWEL-1391] Add ten built-in grammars to the standalone highlighter

closes https://github.com/JetBrains/intellij-community/pull/3617


(cherry picked from commit ba037c0ccc1068128b79dbb17347b584ab7de7de)

IJ-MR-220560

GitOrigin-RevId: fcc8d78260fc080dca9b7d5f0e813efb2d7898a5
This commit is contained in:
Daniel Bertoldi
2026-09-07 22:29:10 +00:00
committed by intellij-monorepo-bot
parent e40150c5fe
commit b0fd16a790
38 changed files with 4290 additions and 77 deletions
@@ -35,7 +35,19 @@ plugins/textmate/lib/bundles/java/syntaxes/java.tmLanguage.json
The fields you need: `match` (single-line pattern), `begin`/`end` (block patterns like strings and comments), and
`captures` (group-to-scope mappings). Look for scopes that map to `TokenType` values: `keyword.*`, `string.*`,
`comment.*`, `storage.type`/`support.type`, `constant.*`, `constant.numeric`, and `entity.name.function`.
`comment.*`, `storage.type`/`support.type`, `support.type.property-name` (data-language keys), `constant.*`,
`constant.numeric`, and `entity.name.function`.
Scopes with no `TokenType` equivalent are meant to be skipped: `meta.*` (structural context), `punctuation.*`, and
`invalid.*` (error highlighting, which this highlighter deliberately doesn't do).
When a scope could plausibly map to more than one `TokenType`, check what the TextMate plugin itself does before
deciding. `plugins/textmate/src/.../highlighting/TextMateDefaultColorsProvider.java` maps each scope prefix to a
`TextAttributesKey`; `platform/core-api/src/com/intellij/openapi/editor/DefaultLanguageHighlighterColors.java` gives
that key its fallback; and `platform/platform-resources/src/DefaultColorSchemesManager.xml` holds the values for both
Default and Darcula. Two mappings there are easy to get wrong from intuition: `storage.type` is a **keyword**, not a
type, so C's `int` and `size_t` are keywords; and `keyword.operator` is `DEFAULT_OPERATION_SIGN`, a key of its own that
neither default scheme colors, which is what `TokenType.OPERATOR` exists for.
If the IntelliJ bundle doesn't cover your language, check the
[shikijs/textmate-grammars-themes](https://github.com/shikijs/textmate-grammars-themes) repo or the
@@ -44,6 +56,19 @@ If the IntelliJ bundle doesn't cover your language, check the
tmLanguage grammars use [Oniguruma](https://macromates.com/manual/en/regular_expressions) RegEx library. See [Regex engine limitations](#regex-engine-limitations) for what breaks
when porting to Java's regex engine.
### Watch for patterns that rely on the grammar tree
A tmLanguage grammar is a set of regexes **plus a tree** deciding which of them are eligible where. We only have
the regexes: our rule list is flat and every rule is tried at every position. So a pattern that was safe upstream
because it could only be reached in one context may misfire once it's tried everywhere.
For example, CSS scopes `#tag-names` under `#selector` and `#property-values` under `#rule-list`, so they can
never both apply. Flattened, the ~20 words that are in both lists collide, and `display: table` colors `table` as
an HTML tag.
When a pattern depends on context, re-encode that context in the pattern, or leave the rule out. Either way, say
which you did in the file header, and mark any lookaround you add as yours rather than the bundle's.
Add a comment at the top of the file pointing to the source:
```kotlin
@@ -110,18 +135,30 @@ internal object BuiltInLanguageGrammars {
}
```
When opening the PR, make sure to test all possible keywords for a language. Feel free to mix and match regexes,
sometimes TextMate won't highlight a keyword that GitHub does, and vice versa. Just make sure the highlighting is being
properly applied. Here's a very simple template for testing:
When opening the PR, run a sample of real code through the grammar and check every `TokenType` the language can
produce. Reading the patterns is not enough — the flat-model misfires described above only show up in the output.
Here's a very simple template for testing:
```
Keywords, types, constants:
Keywords (control flow, declarations, modifiers):
<add examples>
Types:
<add examples>
Constants and language literals:
<add examples>
Builtins (support.type and support.function: stdlib types, well-known globals):
<add examples>
Method declarations + calls:
<add examples>
Control flow:
Property keys (JSON/YAML/TOML keys, CSS property names, HTML attributes):
<add examples>
Operators (`+`, `==`, `&&`, `|`, and word operators like `instanceof`, `typeof`, `in`):
<add examples>
Comments:
@@ -134,6 +171,9 @@ Strings:
<add examples>
```
It goes without saying but do leave out the sections a language doesn't have. Most have no property keys, and Kotlin has
no operator rules, for instance.
#### Registering as an additional language or overriding the LanguageGrammar for a built-in language
To highlight a language that isn't built in or override an existing one, just pass your `LanguageGrammar`
@@ -221,9 +261,11 @@ pattern is compiled once when the `TokenRule` is created.
| `constant(pattern)` | group 0 → CONSTANT | Entire match colored as constant |
| `number(pattern)` | group 0 → NUMBER | Entire match colored as number |
| `builtin(pattern)` | group 0 → BUILTIN | Entire match colored as builtin |
| `propertyKey(pattern)` | group 0 → PROPERTY_KEY | Entire match colored as a data-language key (JSON/YAML/TOML) |
| `operator(pattern)` | group 0 → OPERATOR | Entire match colored as an operator (`+`, `==`, `&&`, `\|`) |
| `functionCall(pattern)` | group 1 → FUNCTION_CALL | Group 1 must isolate the function name |
| `functionDeclaration(pattern)` | group 1 → KEYWORD, group 2 → FUNCTION_CALL | Group 1 = keyword, group 2 = name |
| `typeDeclaration(pattern)` | group 1 → KEYWORD, group 2 → BUILTIN | Group 1 = keyword, group 2 = type name |
| `typeDeclaration(pattern)` | group 1 → KEYWORD, group 2 → TYPE | Group 1 = keyword, group 2 = type name |
If the regex has fewer groups than the factory expects, the missing groups are silently skipped and no span is emitted.
@@ -238,14 +280,26 @@ TokenRule(
## Regex engine limitations
Patterns run through Java's `java.util.regex`. Most tmLanguage patterns work, but a few `PCRE`/`Oniguruma` features
don't exist in Java:
Patterns run through Java's `java.util.regex`. Most tmLanguage patterns work as written. These do not:
- **POSIX character classes** (`[[:alpha:]]`, etc.) -> use `[a-zA-Z]` etc. instead.
- **Variable-length lookbehind** -> only fixed-width works (`(?<=fun )` is fine, `(?<=fun\s+)` is not). Rewrite as
a capturing-group rule.
- **Named backreferences**, **conditional patterns**, **subroutine calls**, and **recursive patterns** -> not
supported.
- **`\p{Surrogate}`** -> use `\p{Cs}`.
- **Multi-codepoint escapes** (`\x{FEFF FFFE FFFF}`) -> split into one escape each.
- **Open-ended repetition** (`\d{,2}`) -> Oniguruma reads that as `{0,2}`; Java raises "Illegal repetition".
Write the zero.
- **`#` and spaces inside a character class under `(?x)`** -> Java strips both, Oniguruma does not. The C
bundle's printf rule contains `[#0\- +']`, where the `#` opens a comment that swallows the rest of the line
and leaves the class unclosed. Escape them: `[\#0\-\ +']`.
- **Named backreferences**, **conditional patterns**, **subroutine calls** and **recursive patterns** -> not
supported. Plain numeric backreferences (`\2`) are fine, which is what lets a heredoc rule match its own
closing delimiter.
- **`\G`** -> anchors to the search start, so it pins a rule to the cursor and breaks earliest-match-wins. Drop
it and re-encode whatever context it stood for.
Java is more permissive than the usual rule of thumb in several places, so test before rewriting a pattern. It
accepts variable-length lookbehind, including `+` and `*` (`(?<=fun\s+)x` both compiles and matches), and
alternations of differing length inside one (`(?<=^|[;&]|then )`). It also accepts `(?-mix:...)`, possessive
quantifiers, atomic groups, `\p{Cntrl}`, `\x{85}` and nested class unions like `[\x{85}[^abc]]`.
`additionalGrammars` is searched before the built-in list, so you can also use it to override a built-in grammar for
an existing language.
@@ -0,0 +1,30 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.code.highlighting
import kotlin.test.assertTrue
import org.jetbrains.jewel.intui.standalone.code.highlighting.BuiltInLanguageGrammars
import org.junit.jupiter.api.Test
internal class BuiltInLanguageGrammarsTest {
// A \G whose backslash isn't itself escaped, so a literal \\G doesn't count as the anchor.
private val backtrackingAnchor = Regex("""(?<!\\)(?:\\\\)*\\G""")
@Test
fun `no built-in grammar uses the backslash-G anchor`() {
// \G anchors to the search start, which invalidates the next-match cache in SimpleCodeHighlighter and
// produces wrong spans. See docs/standalone-code-highlighting.md.
val offenders =
BuiltInLanguageGrammars.all.flatMap { grammar ->
grammar.rules
.map { it.pattern }
.filter { backtrackingAnchor.containsMatchIn(it) }
.map { grammar.name to it }
}
assertTrue(
offenders.isEmpty(),
"These built-in rules use \\G, which breaks the next-match cache in SimpleCodeHighlighter:\n" +
offenders.joinToString("\n") { (language, pattern) -> " $language: $pattern" },
)
}
}
@@ -21,6 +21,8 @@ internal val testColors =
comment = Color.Gray,
number = Color.Magenta,
builtin = Color.White,
propertyKey = Color.Black,
operator = Color.DarkGray,
)
/** All [SpanStyle]s covering character offset [index], in [spanStyles] order. */
@@ -128,4 +128,30 @@ internal class SimpleCodeHighlighterTest {
assertEquals(Color.Red, result.colorAt(0)) // Red = keyword in testColors
assertNull(result.colorAt(4)) // "myFunc" — no span for missing group 2
}
@Test
fun `a rule that only matches late in the input is still applied`() = runTest {
// The tokenizer caches each rule's next match. A rule whose first match is far from the start must
// not be dropped, and a rule that has already matched must keep matching after the cursor passes it.
val grammar =
LanguageGrammar(
name = "test",
rules = listOf(TokenRule.keyword("\\bfun\\b"), TokenRule.number("\\b\\d+\\b")),
)
val code = "fun " + "a ".repeat(400) + "42"
val result = SimpleCodeHighlighter(testColors, listOf(grammar)).highlight(code, "test").first()
assertEquals(Color.Red, result.colorAt(0)) // "fun" at the very start
assertEquals(Color.Magenta, result.colorAt(code.length - 1)) // "42" at the very end
}
@Test
fun `every occurrence of a repeating rule is matched`() = runTest {
// Guards the cache invalidation: the cached match must be refreshed once the cursor passes it
val grammar = LanguageGrammar(name = "test", rules = listOf(TokenRule.keyword("\\bfun\\b")))
val code = List(50) { "fun x" }.joinToString(" ")
val result = SimpleCodeHighlighter(testColors, listOf(grammar)).highlight(code, "test").first()
assertEquals(50, result.spanStyles.size)
}
}
@@ -49,6 +49,18 @@ internal class TokenRuleTest {
assertEquals(mapOf(0 to TokenType.BUILTIN), rule.captures)
}
@Test
fun `propertyKey factory colors entire match`() {
val rule = TokenRule.propertyKey("\"(?:[^\"\\\\]|\\\\.)*\"(?=\\s*:)")
assertEquals(mapOf(0 to TokenType.PROPERTY_KEY), rule.captures)
}
@Test
fun `operator factory colors entire match`() {
val rule = TokenRule.operator("&&|\\|\\|")
assertEquals(mapOf(0 to TokenType.OPERATOR), rule.captures)
}
@Test
fun `functionCall factory colors group 1`() {
val rule = TokenRule.functionCall("\\b([A-Za-z_]\\w*)\\s*(?=\\()")
@@ -62,8 +74,8 @@ internal class TokenRuleTest {
}
@Test
fun `typeDeclaration factory colors group 1 as keyword and group 2 as builtin`() {
fun `typeDeclaration factory colors group 1 as keyword and group 2 as type`() {
val rule = TokenRule.typeDeclaration("\\b(class)\\s+([A-Za-z_]\\w*)")
assertEquals(mapOf(1 to TokenType.KEYWORD, 2 to TokenType.BUILTIN), rule.captures)
assertEquals(mapOf(1 to TokenType.KEYWORD, 2 to TokenType.TYPE), rule.captures)
}
}
@@ -0,0 +1,223 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.code.highlighting.languages
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.jetbrains.jewel.intui.code.highlighting.colorAt
import org.jetbrains.jewel.intui.code.highlighting.testColors
import org.jetbrains.jewel.intui.standalone.code.highlighting.SimpleCodeHighlighter
import org.junit.jupiter.api.Test
internal class CGrammarTest {
private val highlighter = SimpleCodeHighlighter(testColors)
private suspend fun highlight(code: String, language: String = "c") = highlighter.highlight(code, language).first()
@Test
fun `name and all aliases are recognized`() = runTest {
for (identifier in listOf("c", "h", "i", "cats", "idc")) {
assertTrue(highlight("int x;", identifier).spanStyles.isNotEmpty(), "Alias '$identifier' not recognized")
}
}
@Test
fun `line and block comments are colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("// a comment").colorAt(0))
assertEquals(testColors.comment, highlight("/* a\nb */").colorAt(0))
assertEquals(testColors.comment, highlight("int x; // trailing").colorAt(7))
}
@Test
fun `keywords and strings inside a comment are not colored separately`() = runTest {
assertEquals(testColors.comment, highlight("// int x").colorAt(3))
assertEquals(testColors.comment, highlight("/* \"str\" */").colorAt(4))
}
@Test
fun `strings and character literals are colored as string`() = runTest {
assertEquals(testColors.string, highlight("puts(\"hi\");").colorAt(5))
assertEquals(testColors.string, highlight("char c = 'a';").colorAt(9))
assertEquals(testColors.string, highlight("puts(\"a\\\"b\");").colorAt(5))
}
@Test
fun `an escape inside a string stays string-colored`() = runTest {
// The bundle scopes \n and %d separately, but only reaches those rules inside the string
assertEquals(testColors.string, highlight("puts(\"%d\\n\");").colorAt(6))
}
@Test
fun `control flow keywords are colored as keyword`() = runTest {
for (keyword in listOf("break", "continue", "do", "else", "for", "goto", "if", "return", "while")) {
assertEquals(testColors.keyword, highlight("$keyword ").colorAt(0), "'$keyword' should be a keyword")
}
}
@Test
fun `switch case and default are colored as keyword`() = runTest {
for (keyword in listOf("switch", "case", "default")) {
assertEquals(testColors.keyword, highlight("$keyword ").colorAt(0), "'$keyword' should be a keyword")
}
}
@Test
fun `aggregate keywords are colored as keyword`() = runTest {
// The bundle files enum, struct and union under storage.type; we read them as declaration keywords
for (keyword in listOf("enum", "struct", "union")) {
assertEquals(testColors.keyword, highlight("$keyword S").colorAt(0), "'$keyword' should be a keyword")
}
}
@Test
fun `storage modifiers and typedef are colored as keyword`() = runTest {
for (keyword in listOf("const", "extern", "register", "restrict", "static", "volatile", "inline")) {
assertEquals(testColors.keyword, highlight("$keyword int x").colorAt(0), "'$keyword' should be a keyword")
}
assertEquals(testColors.keyword, highlight("typedef int myint;").colorAt(0))
}
@Test
fun `a keyword appearing inside an identifier is not colored`() = runTest {
// The bundle's typedef rule is the bare word, so without the \b pair we add, the substring matches
assertNull(highlight("int mytypedefName;").colorAt(6))
assertNull(highlight("int my_size_typedef;").colorAt(12))
// Same check for the neighbours that already carried boundaries
assertNull(highlight("int constant;").colorAt(4))
assertNull(highlight("int switcher;").colorAt(4))
assertNull(highlight("int defaults;").colorAt(4))
}
@Test
fun `storage type names are colored as keyword`() = runTest {
// IntelliJ maps the whole storage.type family to its keyword key, so `int` is not a TYPE here
for (type in listOf("int", "char", "void", "float", "double", "unsigned", "short", "long", "_Bool")) {
assertEquals(testColors.keyword, highlight("$type x").colorAt(0), "'$type' should be a keyword")
}
// These are in storage.type.built-in.c as well as in a support.type table, and the bundle lists
// #storage_types first
for (type in listOf("size_t", "uint32_t", "pthread_t", "time_t", "ssize_t")) {
assertEquals(testColors.keyword, highlight("$type x").colorAt(0), "'$type' should be a keyword")
}
}
@Test
fun `support type names are colored as builtin`() = runTest {
// support.type maps to IntelliJ's predefined-symbol key, which is our BUILTIN
assertEquals(testColors.builtin, highlight("UInt32 x").colorAt(0))
assertEquals(testColors.builtin, highlight("my_custom_t x").colorAt(0), "support.type.posix-reserved")
}
@Test
fun `language constants are colored as constant`() = runTest {
for (constant in listOf("NULL", "true", "false", "TRUE", "FALSE")) {
assertEquals(testColors.constant, highlight("x = $constant;").colorAt(4), "'$constant' is a constant")
}
}
@Test
fun `predefined macros are colored as constant`() = runTest {
assertEquals(testColors.constant, highlight("__FILE__").colorAt(0))
}
@Test
fun `mac classic prefixes are honored`() = runTest {
assertEquals(testColors.constant, highlight("kMaxSize").colorAt(0))
assertEquals(testColors.builtin, highlight("gState").colorAt(0))
assertEquals(testColors.builtin, highlight("sBuffer").colorAt(0))
}
@Test
fun `numbers are colored as number`() = runTest {
for (number in listOf("42", "0xFF", "0b1010", "0755", "3.14", "1e-10", "3.14f", "10UL")) {
assertEquals(testColors.number, highlight("x = $number;").colorAt(4), "'$number' should be a number")
}
}
@Test
fun `function calls and definitions color the name`() = runTest {
assertEquals(testColors.functionCall, highlight("puts(\"hi\");").colorAt(0))
assertEquals(testColors.functionCall, highlight("int main(void) { return 0; }").colorAt(4))
}
@Test
fun `a keyword followed by a paren is not a function call`() = runTest {
for (keyword in listOf("if", "while", "return", "for", "switch")) {
assertEquals(testColors.keyword, highlight("$keyword (x)").colorAt(0), "'$keyword (' should stay a keyword")
}
// sizeof is keyword.operator.sizeof.c in the bundle, not a control keyword
assertEquals(testColors.operator, highlight("sizeof (x)").colorAt(0))
}
@Test
fun `the call rule does not slide past a word it excludes`() = runTest {
// The bundle's exclusion list is an unanchored lookahead. `catch` is on it but is not a C keyword,
// so without the leading boundary we added the rule would fail at `c` and match `atch (` instead.
val result = highlight("catch (x)")
assertNull(result.colorAt(0))
assertNull(result.colorAt(1))
}
@Test
fun `member access colors the member but not the object`() = runTest {
assertEquals(testColors.builtin, highlight("p->count").colorAt(3))
assertNull(highlight("p->count").colorAt(0))
assertEquals(testColors.builtin, highlight("s.len").colorAt(2))
}
@Test
fun `member access wins over the operator rules`() = runTest {
// The match starts at the `-`, where the minus operator ties with it
assertEquals(testColors.builtin, highlight("argv[i]->len").colorAt(9))
assertEquals(testColors.operator, highlight("a - b").colorAt(2), "a bare minus is still an operator")
}
@Test
fun `preprocessor directives are colored as keyword`() = runTest {
for (directive in listOf("if", "ifdef", "ifndef", "elif", "else", "endif")) {
assertEquals(testColors.keyword, highlight("#$directive X\n").colorAt(0), "'#$directive'")
}
for (directive in listOf("pragma once", "error nope", "undef X", "line 5")) {
assertEquals(testColors.keyword, highlight("#$directive").colorAt(0), "'#$directive'")
}
}
@Test
fun `include colors the directive and the path`() = runTest {
assertEquals(testColors.keyword, highlight("#include <stdio.h>").colorAt(0))
assertEquals(testColors.string, highlight("#include <stdio.h>").colorAt(10))
assertEquals(testColors.string, highlight("#include \"local.h\"").colorAt(10))
}
@Test
fun `define colors the directive and the macro name`() = runTest {
assertEquals(testColors.keyword, highlight("#define MAX 10").colorAt(0))
assertEquals(testColors.functionCall, highlight("#define MAX 10").colorAt(8))
assertEquals(testColors.functionCall, highlight("#define SQ(x) ((x)*(x))").colorAt(8))
}
@Test
fun `an if 0 block is greyed out`() = runTest {
val result = highlight("#if 0\ndead();\n#endif\n")
assertEquals(testColors.comment, result.colorAt(0))
assertEquals(testColors.comment, result.colorAt(6))
}
@Test
fun `an if 1 block stays live`() = runTest {
assertEquals(testColors.functionCall, highlight("#if 1\nlive();\n#endif\n").colorAt(6))
}
@Test
fun `operators are colored as operator`() = runTest {
// keyword.operator.* is DEFAULT_OPERATION_SIGN in IntelliJ, a different key from keywords
for (expression in listOf("a + b", "a << b", "a == b", "a && b")) {
assertEquals(testColors.operator, highlight(expression).colorAt(2), "'$expression'")
}
assertEquals(testColors.operator, highlight("i++").colorAt(1))
assertEquals(testColors.operator, highlight("a ? b : c").colorAt(2))
assertEquals(testColors.operator, highlight("sizeof(x)").colorAt(0))
}
}
@@ -0,0 +1,319 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.code.highlighting.languages
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.jetbrains.jewel.intui.code.highlighting.colorAt
import org.jetbrains.jewel.intui.code.highlighting.spansAt
import org.jetbrains.jewel.intui.code.highlighting.testColors
import org.jetbrains.jewel.intui.standalone.code.highlighting.SimpleCodeHighlighter
import org.junit.jupiter.api.Test
internal class CSSGrammarTest {
private val highlighter = SimpleCodeHighlighter(testColors)
private suspend fun highlight(code: String, language: String = "css") =
highlighter.highlight(code, language).first()
@Test fun `name is recognized`() = runTest { assertTrue(highlight("a { color: red; }").spanStyles.isNotEmpty()) }
@Test
fun `comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("/* a comment */").colorAt(0))
}
@Test
fun `property name is colored as property key`() = runTest {
// "color" starts at index 4 in "a { color: red; }"
assertEquals(testColors.propertyKey, highlight("a { color: red; }").colorAt(4))
}
@Test
fun `custom property is colored as property key`() = runTest {
// "--brand" starts at index 4 in "a { --brand: red; }"
assertEquals(testColors.propertyKey, highlight("a { --brand: red; }").colorAt(4))
}
@Test
fun `vendored property name is colored as property key`() = runTest {
assertEquals(testColors.propertyKey, highlight("a { -webkit-box-shadow: none; }").colorAt(4))
}
@Test
fun `element selector is colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("a { color: red; }").colorAt(0))
}
@Test
fun `custom element selector is colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("my-widget { color: red; }").colorAt(0))
}
@Test
fun `class selector is colored as type`() = runTest {
assertEquals(testColors.type, highlight(".foo { color: red; }").colorAt(0))
}
@Test
fun `class selector may contain digits and double hyphens`() = runTest {
assertEquals(testColors.type, highlight(".card--wide { color: red; }").colorAt(0))
assertEquals(testColors.type, highlight(".col-6 { color: red; }").colorAt(0))
}
@Test
fun `id selector is colored as type`() = runTest {
assertEquals(testColors.type, highlight("#foo { color: red; }").colorAt(0))
}
@Test
fun `an id selector that looks like a hex color is still a selector`() = runTest {
// `#foo` above never reached the hex rule, since `o` is not a hex digit. These do.
assertEquals(testColors.type, highlight("#abc { color: red; }").colorAt(0))
assertEquals(testColors.type, highlight("#abcdef { color: red; }").colorAt(0))
assertEquals(testColors.type, highlight("#abc, #def { color: red; }").colorAt(6))
assertEquals(testColors.type, highlight("a:hover #abc { color: red; }").colorAt(8))
assertEquals(testColors.type, highlight("a { color: red; } #abc { color: red; }").colorAt(18))
}
@Test
fun `a hyphenated id selector is colored whole, not just its hex-looking prefix`() = runTest {
val result = highlight("#abc-def { color: red; }")
assertEquals(testColors.type, result.colorAt(0))
assertEquals(testColors.type, result.colorAt(5))
}
@Test
fun `pseudo-class is colored as builtin`() = runTest {
val result = highlight("a:hover { color: red; }")
assertEquals(testColors.keyword, result.colorAt(0), "'a' is still an element selector")
assertEquals(testColors.builtin, result.colorAt(1), "':hover' should be a pseudo-class")
assertEquals(testColors.builtin, result.colorAt(2), "the name is part of the same match")
}
@Test
fun `pseudo-element is colored as builtin`() = runTest {
assertEquals(testColors.builtin, highlight("a::before { color: red; }").colorAt(1))
}
@Test
fun `pseudo-class is not mistaken for a property`() = runTest {
// The grammar's property-name list does not contain `hover`, and the pseudo-class rule owns the colon
assertNotEquals(testColors.propertyKey, highlight("a:hover { color: red; }").colorAt(0))
}
@Test
fun `functional pseudo-class colors its nth expression as a number`() = runTest {
val result = highlight("li:nth-child(2n+1) { color: red; }")
assertEquals(testColors.builtin, result.colorAt(2))
assertEquals(testColors.number, result.colorAt(13))
}
@Test
fun `functional pseudo-class colors its parity keyword as builtin`() = runTest {
assertEquals(testColors.builtin, highlight("li:nth-child(odd) { color: red; }").colorAt(13))
}
@Test
fun `negation pseudo-class is colored as builtin`() = runTest {
assertEquals(testColors.builtin, highlight("a:not(.x) { color: red; }").colorAt(1))
}
@Test
fun `a value that is also a tag name stays a value without a trailing semicolon`() = runTest {
// Regression: about twenty words are in both the tag-name and property-value lists. The bundle keeps
// them apart by nesting; flattened, `table` matched the tag rule because `\s` is in its lookahead set
// and `}` follows. Declarations ending in `;` were never affected.
for (value in listOf("table", "small", "ruby", "progress", "menu")) {
assertEquals(
testColors.builtin,
highlight("a { display: $value }").colorAt(13),
"'$value' without a trailing semicolon should still be a property value",
)
assertEquals(
testColors.builtin,
highlight("a { display: $value; }").colorAt(13),
"'$value' with a trailing semicolon should be a property value",
)
}
}
@Test
fun `the same words are still tag names in selector position`() = runTest {
// The other half of the guard: it must not cost us selector highlighting
for (tag in listOf("table", "small", "ruby", "progress", "menu", "code")) {
assertEquals(
testColors.keyword,
highlight("$tag { color: red; }").colorAt(0),
"'$tag' in selector position should be a tag name",
)
}
}
@Test
fun `hex color is colored as constant`() = runTest {
// "#fff" starts at index 11 in "a { color: #fff; }"
assertEquals(testColors.constant, highlight("a { color: #fff; }").colorAt(11))
}
@Test
fun `a hex color in value position is a color, not an id`() = runTest {
val result = highlight("a { color: #abc; }")
assertEquals(testColors.constant, result.colorAt(11))
assertNotEquals(testColors.type, result.colorAt(11), "'#abc' in a value is a color, not an id")
// Every shape the hex rule accepts, in the positions a value can take
assertEquals(testColors.constant, highlight("a { color: #abc }").colorAt(11))
assertEquals(testColors.constant, highlight("a { color:#abc; }").colorAt(10))
assertEquals(testColors.constant, highlight("a { color: #abcd; }").colorAt(11))
assertEquals(testColors.constant, highlight("a { color: #aabbccdd; }").colorAt(11))
assertEquals(testColors.constant, highlight("a { border: 1px solid #ff0000; }").colorAt(22))
assertEquals(
testColors.constant,
highlight("a { background: linear-gradient(#abc, #def); }").colorAt(32),
"hex inside a function call",
)
}
@Test
fun `document-rule functions are colored as function call`() = runTest {
// support.function.document-rule.css — each name starts at index 10 in `@document <name>("x") { }`
for (function in listOf("url-prefix", "domain", "regexp")) {
assertEquals(
testColors.functionCall,
highlight("@document $function(\"x\") { }").colorAt(10),
"'$function' should be colored as a function call",
)
}
}
@Test
fun `at-rule is colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("@media screen").colorAt(0))
assertEquals(testColors.keyword, highlight("@font-face { }").colorAt(0))
}
@Test
fun `vendor-prefixed at-rule is colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("@-webkit-keyframes spin { }").colorAt(0))
}
@Test
fun `important is colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("!important").colorAt(0))
assertEquals(testColors.keyword, highlight("a { color: red !important; }").colorAt(15))
}
@Test
fun `numbers with units are colored as number`() = runTest {
// "10px" starts at index 11 in "a { width: 10px; }"
assertEquals(testColors.number, highlight("a { width: 10px; }").colorAt(11))
assertEquals(testColors.number, highlight("a { width: 50%; }").colorAt(11))
assertEquals(testColors.number, highlight("a { margin: -1.5em; }").colorAt(12))
}
@Test
fun `unit gets its own keyword span`() = runTest {
// The bundle scopes the unit keyword.other.unit.*.css, so "rem" is both inside the number span and its own
// keyword span. "rem" starts at index 12 in "a { width: 2rem; }".
assertTrue(highlight("a { width: 2rem; }").spansAt(12).any { it.color == testColors.keyword })
}
@Test
fun `decimal without a leading digit is a number, not a class`() = runTest {
assertEquals(testColors.number, highlight("a { opacity: .5; }").colorAt(13))
assertEquals(testColors.number, highlight("a { color: rgba(0, 0, 0, .5); }").colorAt(25))
}
@Test
fun `string is colored as string`() = runTest {
// The opening quote is at index 13 in "a { content: \"x\"; }"
assertEquals(testColors.string, highlight("a { content: \"x\"; }").colorAt(13))
assertEquals(testColors.string, highlight("a { content: '\\201C'; }").colorAt(13))
}
@Test
fun `color keyword is colored as builtin`() = runTest {
assertEquals(testColors.builtin, highlight("a { color: red; }").colorAt(11))
assertEquals(testColors.builtin, highlight("a { color: rebeccapurple; }").colorAt(11))
assertEquals(testColors.builtin, highlight("a { color: currentColor; }").colorAt(11))
}
@Test
fun `property value keyword is colored as builtin`() = runTest {
assertEquals(testColors.builtin, highlight("a { display: flex; }").colorAt(13))
assertEquals(testColors.builtin, highlight("a { list-style-type: lower-roman; }").colorAt(21))
assertEquals(testColors.builtin, highlight("a { font-family: helvetica; }").colorAt(17))
assertEquals(testColors.builtin, highlight("a { color: -moz-fixed; }").colorAt(11))
}
@Test
fun `function name is colored as function call`() = runTest {
assertEquals(testColors.functionCall, highlight("a { width: calc(1px); }").colorAt(11))
assertEquals(testColors.functionCall, highlight("a { color: rgba(0,0,0,.5); }").colorAt(11))
assertEquals(testColors.functionCall, highlight("a { width: min(1px, 2px); }").colorAt(11))
assertEquals(testColors.functionCall, highlight("a { clip-path: circle(1px); }").colorAt(15))
assertEquals(testColors.functionCall, highlight("a { transform: translateX(1px); }").colorAt(15))
assertEquals(testColors.functionCall, highlight("a { transition-timing-function: steps(2); }").colorAt(32))
}
@Test
fun `gradient url and var are colored as function calls`() = runTest {
assertEquals(testColors.functionCall, highlight("a { background: linear-gradient(red, blue); }").colorAt(16))
assertEquals(testColors.functionCall, highlight("a { background: url(x.png); }").colorAt(16))
assertEquals(testColors.functionCall, highlight("a { color: var(--x); }").colorAt(11))
}
@Test
fun `attribute selector names its attribute and operator`() = runTest {
assertEquals(testColors.propertyKey, highlight("a[href] { color: red; }").colorAt(2))
assertEquals(testColors.operator, highlight("a[href=\"x\"] { color: red; }").colorAt(6))
}
@Test
fun `media feature is colored as property key`() = runTest {
assertEquals(testColors.propertyKey, highlight("@media (min-width: 600px) { }").colorAt(8))
}
@Test
fun `media type and logical operator are colored`() = runTest {
val result = highlight("@media screen and (color) { }")
assertEquals(testColors.builtin, result.colorAt(7), "'screen' is a media type")
assertEquals(testColors.operator, result.colorAt(14), "'and' is a logical operator")
}
@Test
fun `media feature keyword is colored as builtin`() = runTest {
assertEquals(testColors.builtin, highlight("@media (orientation: portrait) { }").colorAt(21))
}
@Test
fun `aspect ratio is colored as two numbers around an operator`() = runTest {
val result = highlight("@media (aspect-ratio: 16/9) { }")
assertEquals(testColors.number, result.colorAt(22))
assertEquals(testColors.operator, result.colorAt(24))
}
@Test
fun `unicode range is colored as constant`() = runTest {
assertEquals(testColors.constant, highlight("a { unicode-range: U+0025-00FF; }").colorAt(19))
}
@Test
fun `combinator is an operator and the wildcard is a tag`() = runTest {
assertEquals(testColors.operator, highlight("a > b { color: red; }").colorAt(2))
assertEquals(testColors.keyword, highlight("* { color: red; }").colorAt(0))
}
@Test
fun `an identifier the bundle does not know is left unstyled`() = runTest {
assertEquals(null, highlight("a { color: zzzz; }").colorAt(11))
}
@Test
fun `shorthand property names from the bundle list are recognized`() = runTest {
assertEquals(testColors.propertyKey, highlight("a { background-position-x: 0; }").colorAt(4))
assertEquals(testColors.propertyKey, highlight("a { overflow-y: hidden; }").colorAt(4))
}
}
@@ -0,0 +1,194 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.code.highlighting.languages
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.jetbrains.jewel.intui.code.highlighting.colorAt
import org.jetbrains.jewel.intui.code.highlighting.testColors
import org.jetbrains.jewel.intui.standalone.code.highlighting.SimpleCodeHighlighter
import org.junit.jupiter.api.Test
internal class HTMLGrammarTest {
private val highlighter = SimpleCodeHighlighter(testColors)
private suspend fun highlight(code: String, language: String = "html") =
highlighter.highlight(code, language).first()
@Test
fun `name and all aliases are recognized`() = runTest {
val identifiers = listOf("html", "htm", "xhtml", "xht", "shtml", "mdoc", "jshtm", "volt", "ejs", "rhtml")
for (identifier in identifiers) {
assertTrue(highlight("<div>", identifier).spanStyles.isNotEmpty(), "Alias '$identifier' not recognized")
}
}
@Test
fun `comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("<!-- a comment -->").colorAt(0))
}
@Test
fun `tag inside a comment is not colored as a tag`() = runTest {
// "<" of the inner tag is at index 5 in "<!-- <div> -->"
assertEquals(testColors.comment, highlight("<!-- <div> -->").colorAt(5))
}
@Test
fun `doctype name is colored as keyword`() = runTest {
// Only the DOCTYPE word carries entity.name.tag.html; the leading "<!" is punctuation, and the
// bundle scopes "html" through a doctype-internal rule that needs a tag context to be ported.
val highlighted = highlight("<!DOCTYPE html>")
assertEquals(testColors.keyword, highlighted.colorAt(2))
assertNull(highlighted.colorAt(0))
assertNull(highlighted.colorAt(10))
}
@Test
fun `xml processing instruction name is colored as keyword`() = runTest {
// "xml" starts at index 2 in `<?xml version="1.0"?>`
assertEquals(testColors.keyword, highlight("<?xml version=\"1.0\"?>").colorAt(2))
}
@Test
fun `cdata content is colored as string`() = runTest {
// "raw" starts at index 10 in "<![CDATA[ raw ]]>"; the delimiters are punctuation
val highlighted = highlight("<![CDATA[ raw ]]>")
assertEquals(testColors.string, highlighted.colorAt(10))
assertNull(highlighted.colorAt(0))
}
@Test
fun `tag name is colored as keyword`() = runTest {
val highlighted = highlight("<div>")
assertEquals(testColors.keyword, highlighted.colorAt(1))
assertNull(highlighted.colorAt(0))
}
@Test
fun `closing tag name is colored as keyword`() = runTest {
// "div" starts at index 2 in "</div>"
assertEquals(testColors.keyword, highlight("</div>").colorAt(2))
}
@Test
fun `heading and void tag names are colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("<h1>").colorAt(1))
assertEquals(testColors.keyword, highlight("<thead>").colorAt(1))
assertEquals(testColors.keyword, highlight("<br/>").colorAt(1))
}
@Test
fun `script and style tag names are colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("<script>").colorAt(1))
assertEquals(testColors.keyword, highlight("</script>").colorAt(2))
assertEquals(testColors.keyword, highlight("<style>").colorAt(1))
assertEquals(testColors.keyword, highlight("</style>").colorAt(2))
}
@Test
fun `custom element name is colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("<my-widget>").colorAt(1))
assertEquals(testColors.keyword, highlight("</my-widget>").colorAt(2))
}
@Test
fun `obsolete and unrecognized tag names are still colored as keyword`() = runTest {
// The bundle scopes these as entity.name.tag.html plus an invalid.* scope that this highlighter skips
assertEquals(testColors.keyword, highlight("<applet>").colorAt(1))
assertEquals(testColors.keyword, highlight("<blink>").colorAt(1))
assertEquals(testColors.keyword, highlight("<UNKNOWNTAG>").colorAt(1))
}
@Test
fun `attribute name is colored as property key`() = runTest {
// "href" starts at index 3 in `<a href="x">`
val highlighted = highlight("<a href=\"x\">")
assertEquals(testColors.propertyKey, highlighted.colorAt(3))
assertEquals(testColors.keyword, highlighted.colorAt(1))
}
@Test
fun `data and event handler attributes are colored as property key`() = runTest {
// Each of these attribute names starts at index 5
assertEquals(testColors.propertyKey, highlight("<div data-foo=\"1\">").colorAt(5))
assertEquals(testColors.propertyKey, highlight("<div onclick=\"f()\">").colorAt(5))
assertEquals(testColors.propertyKey, highlight("<div style=\"a\">").colorAt(5))
}
@Test
fun `an attribute name buried in a longer name is not colored`() = runTest {
// The bundle's names carry a trailing (?![\w:-]) but no leading guard, so without the one we add
// these match a suffix and color only part of the name.
assertNull(highlight("<div mytitle=\"x\">").colorAt(7), "html5 name as a suffix")
assertNull(highlight("<div myonclick=\"f()\">").colorAt(7), "event handler as a suffix")
assertNull(highlight("<div xstyle=\"x\">").colorAt(6), "style as a suffix")
assertNull(highlight("<div notdata-foo=\"x\">").colorAt(8), "data- as a suffix")
assertNull(highlight("<div a-title=\"x\">").colorAt(7), "after a hyphen")
assertNull(highlight("<div xml:lang=\"en\">").colorAt(9), "after a namespace colon")
}
@Test
fun `valueless attributes are not colored`() = runTest {
// The `(?=\s*=)` guard that keeps attribute names out of prose also excludes boolean attributes.
// "type" is at index 7 and "required" at index 19 in `<input type="text" required>`.
val highlighted = highlight("<input type=\"text\" required>")
assertEquals(testColors.propertyKey, highlighted.colorAt(7))
assertNull(highlighted.colorAt(19))
}
@Test
fun `attribute names in text content are not colored`() = runTest {
// Regression: HTML5 attribute names are ordinary English words, and the bundle relies on the grammar
// tree to only ever try them between `<` and `>`. Without the `(?=\s*=)` guard, every one of these
// words was colored as a property key.
for (word in listOf("title", "for", "size", "value", "list", "method", "type", "open")) {
assertNull(highlight("<p>a $word b</p>").colorAt(5), "'$word' should not be colored in text content")
}
}
@Test
fun `prose with no markup is not highlighted at all`() = runTest {
assertTrue(highlight("The list method returns a value of that type.").spanStyles.isEmpty())
}
@Test
fun `deprecated attribute name is not colored`() = runTest {
// The bundle's only scope for align, bgcolor and border is invalid.deprecated.*, which is skipped
assertNull(highlight("<div align=\"x\">").colorAt(5))
}
@Test
fun `attribute value is colored as string`() = runTest {
// The opening quote is at index 8 in `<a href="x">`
assertEquals(testColors.string, highlight("<a href=\"x\">").colorAt(8))
// The opening quote is at index 11 in `<div class='a'>`
assertEquals(testColors.string, highlight("<div class='a'>").colorAt(11))
}
@Test
fun `entities are colored as constant`() = runTest {
for (entity in listOf("&nbsp;", "&amp;", "&#160;", "&#xA0;", "&#XA0;")) {
assertEquals(testColors.constant, highlight(entity).colorAt(0), "'$entity' should be colored as constant")
}
}
@Test
fun `text content is not highlighted`() = runTest {
// "hello" starts at index 3 in "<p>hello</p>"
val highlighted = highlight("<p>hello</p>")
assertNull(highlighted.colorAt(3))
assertEquals(testColors.keyword, highlighted.colorAt(1))
assertEquals(testColors.keyword, highlighted.colorAt(10))
}
@Test
fun `tags on separate lines are highlighted`() = runTest {
// "span" starts at index 9 and its closing name at index 17
val highlighted = highlight("<div>\n <span>x</span>\n</div>")
assertEquals(testColors.keyword, highlighted.colorAt(9))
assertEquals(testColors.keyword, highlighted.colorAt(17))
}
}
@@ -0,0 +1,181 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.code.highlighting.languages
import androidx.compose.ui.text.font.FontStyle
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.jetbrains.jewel.intui.code.highlighting.colorAt
import org.jetbrains.jewel.intui.code.highlighting.spansAt
import org.jetbrains.jewel.intui.code.highlighting.testColors
import org.jetbrains.jewel.intui.standalone.code.highlighting.SimpleCodeHighlighter
import org.junit.jupiter.api.Test
internal class JSONGrammarTest {
private val highlighter = SimpleCodeHighlighter(testColors)
private suspend fun highlight(code: String, language: String = "json") =
highlighter.highlight(code, language).first()
@Test
fun `name and all aliases are recognized`() = runTest {
val identifiers =
listOf(
"json",
"4dform",
"4dproject",
"avsc",
"bowerrc",
"cssmap",
"geojson",
"gltf",
"har",
"ice",
"ipynb",
"jscsrc",
"jslintrc",
"jsmap",
"json.example",
"json-tmlanguage",
"jsonl",
"jsonld",
"mcmeta",
"sarif",
"slnlaunch",
"tact",
"tfstate",
"tfstate.backup",
"topojson",
"tsmap",
"vuerc",
"webapp",
"webmanifest",
"yy",
"yyp",
)
for (identifier in identifiers) {
assertTrue(
highlight("""{"a": 1}""", identifier).spanStyles.isNotEmpty(),
"Alias '$identifier' not recognized",
)
}
}
@Test
fun `language tags are matched case-insensitively`() = runTest {
for (tag in listOf("JSON", "Json", "GeoJSON")) {
assertTrue(highlight("""{"a": 1}""", tag).spanStyles.isNotEmpty(), "Tag '$tag' not recognized")
}
}
@Test
fun `line comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("// a comment").colorAt(0))
}
@Test
fun `block comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("/* block */").colorAt(0))
}
@Test
fun `doc-style block comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("/** doc */").colorAt(0))
}
@Test
fun `comments are italic`() = runTest {
assertEquals(FontStyle.Italic, highlight("// comment").spansAt(0).first().fontStyle)
}
@Test
fun `constant inside comment is not colored as constant`() = runTest {
val result = highlight("// true")
assertEquals(testColors.comment, result.colorAt(3))
assertNotEquals(testColors.constant, result.colorAt(3))
}
@Test
fun `object key is colored as property key`() = runTest {
assertEquals(testColors.propertyKey, highlight("""{"a": 1}""").colorAt(1))
}
@Test
fun `string value is colored as string`() = runTest {
assertEquals(testColors.string, highlight("""{"a": "b"}""").colorAt(6))
}
@Test
fun `key and value with identical text are colored differently`() = runTest {
val result = highlight("""{"a": "a"}""")
assertEquals(testColors.propertyKey, result.colorAt(1), "Key should be a property key")
assertEquals(testColors.string, result.colorAt(6), "Value should be a string")
}
@Test
fun `colon is not part of the key span`() = runTest {
// The key rule's lookahead is zero-width, so the match ends at the closing quote (index 3)
assertNull(highlight("""{"a": 1}""").colorAt(4))
}
@Test
fun `whitespace between key and colon is allowed`() = runTest {
assertEquals(testColors.propertyKey, highlight("""{"a" : 1}""").colorAt(1))
}
@Test
fun `key that looks like a language constant is still a key`() = runTest {
val result = highlight("""{"true": 1}""")
assertEquals(testColors.propertyKey, result.colorAt(2))
assertNotEquals(testColors.constant, result.colorAt(2))
}
@Test
fun `escaped quote does not end the key early`() = runTest {
// Key is `a\":b`, so the quote at index 4 must not terminate it
val result = highlight("""{"a\":b": 1}""")
assertEquals(testColors.propertyKey, result.colorAt(6))
assertEquals(testColors.number, result.colorAt(10))
}
@Test
fun `string in an array is not treated as a key`() = runTest {
assertEquals(testColors.string, highlight("""["a", "b"]""").colorAt(1))
}
@Test
fun `language constants are colored as constant`() = runTest {
for (constant in listOf("true", "false", "null")) {
assertEquals(
testColors.constant,
highlight(constant).colorAt(0),
"'$constant' should be colored as constant",
)
}
}
@Test
fun `constant inside string is not colored as constant`() = runTest {
val result = highlight("""{"a": "true"}""")
assertEquals(testColors.string, result.colorAt(7))
assertNotEquals(testColors.constant, result.colorAt(7))
}
@Test
fun `numbers are colored as number`() = runTest {
for (number in listOf("0", "42", "-1", "3.14", "1e10", "-1.5e+10")) {
assertEquals(testColors.number, highlight(number).colorAt(0), "'$number' should be colored as number")
}
}
@Test
fun `numbers in arrays are colored as number`() = runTest {
// Numbers are not gated on a preceding colon
val result = highlight("[1, 2, 3]")
assertEquals(testColors.number, result.colorAt(1))
assertEquals(testColors.number, result.colorAt(4))
}
}
@@ -0,0 +1,152 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.code.highlighting.languages
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.jetbrains.jewel.intui.code.highlighting.colorAt
import org.jetbrains.jewel.intui.code.highlighting.testColors
import org.jetbrains.jewel.intui.standalone.code.highlighting.SimpleCodeHighlighter
import org.junit.jupiter.api.Test
internal class JSXGrammarTest {
private val highlighter = SimpleCodeHighlighter(testColors)
private suspend fun highlight(code: String, language: String = "jsx") =
highlighter.highlight(code, language).first()
@Test
fun `name and all aliases are recognized`() = runTest {
for (identifier in listOf("jsx", "javascriptreact")) {
assertTrue(highlight("let x = 1", identifier).spanStyles.isNotEmpty(), "Alias '$identifier' not recognized")
}
}
@Test
fun `the javascript grammar does not highlight jsx tags`() = runTest {
// Pins the split from the other side: JAVASCRIPT is registered first, so if it ever reclaims the
// "jsx" alias the tag tests below start resolving to this (unstyled) grammar instead.
assertNull(highlight("<div>", "javascript").colorAt(1))
}
@Test
fun `lowercase tag name is colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("<div>").colorAt(1))
}
@Test
fun `closing tag name is colored as keyword`() = runTest {
// "div" starts at index 2 in "</div>"
assertEquals(testColors.keyword, highlight("</div>").colorAt(2))
}
@Test
fun `capitalized tag name is colored as type`() = runTest {
val result = highlight("<Foo />")
assertEquals(testColors.type, result.colorAt(1))
assertNotEquals(testColors.keyword, result.colorAt(1), "Components should not read as DOM elements")
}
@Test
fun `dotted component name is colored as type`() = runTest {
assertEquals(testColors.type, highlight("<Foo.Bar />").colorAt(1))
}
@Test
fun `attribute name is colored as property key`() = runTest {
// "className" starts at index 5 in `<div className="x">`
assertEquals(testColors.propertyKey, highlight("<div className=\"x\">").colorAt(5))
}
@Test
fun `attribute value is colored as string`() = runTest {
// The opening quote is at index 15 in `<div className="x">`
assertEquals(testColors.string, highlight("<div className=\"x\">").colorAt(15))
}
@Test
fun `attribute with an expression value is colored as property key`() = runTest {
// "onClick" starts at index 5 in "<div onClick={f}>"
assertEquals(testColors.propertyKey, highlight("<div onClick={f}>").colorAt(5))
}
@Test
fun `attribute rule does not fire on javascript assignments`() = runTest {
// Spacing is a convention, not a syntactic boundary, so the rule is anchored to opening-tag
// context instead: an unclosed `<` in expression position, a tag name, then whitespace.
assertNull(highlight("const x = \"y\"").colorAt(6), "spaced assignment")
assertNull(highlight("const x=\"y\";").colorAt(6), "compact assignment")
assertNull(highlight("let a=\"b\", c=\"d\";").colorAt(4), "compact let")
assertNull(highlight("let a=\"b\", c=\"d\";").colorAt(11), "second compact assignment")
assertNull(highlight("obj={k:\"v\"};").colorAt(0), "object literal")
assertNull(highlight("foo.bar=\"baz\";").colorAt(0), "member assignment")
}
@Test
fun `a comparison operator does not open tag context`() = runTest {
assertNull(highlight("if (a<b && c=\"d\") {}").colorAt(11))
assertNull(highlight("x[0]<y && z=\"w\";").colorAt(10), "after a subscript")
assertNull(highlight("f()<g && h=\"i\";").colorAt(9), "after a call")
}
@Test
fun `attributes are still recognized across tag shapes`() = runTest {
assertEquals(testColors.propertyKey, highlight("<div class=\"a\" id=\"b\">").colorAt(15), "second attr")
assertEquals(testColors.propertyKey, highlight("return <div id=\"x\">;").colorAt(12), "after return")
assertEquals(testColors.propertyKey, highlight("<Foo bar={baz}>").colorAt(5), "brace value")
}
@Test
fun `html entity is colored as constant`() = runTest {
for (entity in listOf("&nbsp;", "&#160;", "&#xA0;")) {
assertEquals(testColors.constant, highlight(entity).colorAt(0), "'$entity' should be colored as constant")
}
}
@Test fun `logical and is not mistaken for an entity`() = runTest { assertNull(highlight("a && b").colorAt(2)) }
@Test
fun `less-than in an expression is not mistaken for a tag`() = runTest {
// "b" is at index 6 in "if (a<b) {}"
val result = highlight("if (a<b) {}")
assertEquals(testColors.keyword, result.colorAt(0), "'if' should still be a keyword")
assertNull(result.colorAt(6), "'b' should not read as a tag name")
}
@Test
fun `tag inside a comment is not colored as a tag`() = runTest {
// "<" is at index 3 in "// <div>"
assertEquals(testColors.comment, highlight("// <div>").colorAt(3))
}
@Test
fun `tag inside a string is not colored as a tag`() = runTest {
// "<" is at index 1 in "\"<div>\""
assertEquals(testColors.string, highlight("\"<div>\"").colorAt(1))
}
@Test
fun `attribute rule wins over the javascript keyword rule`() = runTest {
// "for" is a JS keyword, but inside a tag it is an attribute name
assertEquals(testColors.propertyKey, highlight("<label for=\"x\">").colorAt(7))
}
@Test
fun `javascript rules still apply`() = runTest {
assertEquals(testColors.keyword, highlight("const x = 1").colorAt(0))
assertEquals(testColors.number, highlight("const x = 1").colorAt(10))
assertEquals(testColors.comment, highlight("// a comment").colorAt(0))
assertEquals(testColors.builtin, highlight("console").colorAt(0))
}
@Test
fun `component in a return statement is colored as type`() = runTest {
// "Foo" starts at index 8 in "return <Foo />"
val result = highlight("return <Foo />")
assertEquals(testColors.keyword, result.colorAt(0))
assertEquals(testColors.type, result.colorAt(8))
}
}
@@ -109,12 +109,18 @@ internal class JavaGrammarTest {
}
@Test
fun `primitive types are colored as type`() = runTest {
fun `primitive types are colored as keyword`() = runTest {
// storage.type is IntelliJ's keyword key, and its own Java lexer puts INT_KEYWORD in KEYWORD_BIT_SET
for (type in listOf("boolean", "int", "long", "double", "float", "void", "char", "byte")) {
assertEquals(testColors.type, highlight(type).colorAt(0), "'$type' should be colored as type")
assertEquals(testColors.keyword, highlight(type).colorAt(0), "'$type' should be a keyword")
}
}
@Test
fun `instanceof is colored as operator`() = runTest {
assertEquals(testColors.operator, highlight("x instanceof Foo").colorAt(2))
}
@Test
fun `common stdlib roots are colored as builtin`() = runTest {
for (builtin in listOf("String", "Object", "System", "Math")) {
@@ -0,0 +1,261 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.code.highlighting.languages
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.jetbrains.jewel.intui.code.highlighting.colorAt
import org.jetbrains.jewel.intui.code.highlighting.spansAt
import org.jetbrains.jewel.intui.code.highlighting.testColors
import org.jetbrains.jewel.intui.standalone.code.highlighting.SimpleCodeHighlighter
import org.junit.jupiter.api.Test
internal class JavaScriptGrammarTest {
private val highlighter = SimpleCodeHighlighter(testColors)
private suspend fun highlight(code: String, language: String = "javascript") =
highlighter.highlight(code, language).first()
@Test
fun `name and all aliases are recognized`() = runTest {
val identifiers =
listOf(
"javascript",
"js",
"node",
"_js",
"bones",
"cjs",
"es",
"es6",
"frag",
"gs",
"jake",
"jsb",
"jscad",
"jsfl",
"jslib",
"jsm",
"jspre",
"jss",
"mjs",
"njs",
"pac",
"sjs",
"ssjs",
"xsjs",
"xsjslib",
)
for (identifier in identifiers) {
assertTrue(highlight("let x = 1", identifier).spanStyles.isNotEmpty(), "Alias '$identifier' not recognized")
}
}
@Test
fun `line comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("// a comment").colorAt(0))
}
@Test
fun `block comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("/* block */").colorAt(0))
}
@Test
fun `jsdoc comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("/** @param x */").colorAt(0))
}
@Test
fun `comments are italic`() = runTest {
assertEquals(FontStyle.Italic, highlight("// comment").spansAt(0).first().fontStyle)
}
@Test
fun `shebang is colored as comment`() = runTest {
val result = highlight("#!/usr/bin/env node\nlet x = 1")
assertEquals(testColors.comment, result.colorAt(0))
assertEquals(testColors.keyword, result.colorAt(20), "'let' after the shebang should still be a keyword")
}
@Test
fun `shebang only matches at the start of the input`() = runTest {
assertNull(highlight("let x = 1\n#!/usr/bin/env node").colorAt(10))
}
@Test
fun `keyword inside comment is not colored as keyword`() = runTest {
val result = highlight("// const let var")
assertEquals(testColors.comment, result.colorAt(3))
assertNotEquals(testColors.keyword, result.colorAt(3))
}
@Test
fun `double-quoted string is colored as string`() = runTest {
assertEquals(testColors.string, highlight("\"hello\"").colorAt(0))
}
@Test
fun `single-quoted string is colored as string`() = runTest {
assertEquals(testColors.string, highlight("'hello'").colorAt(0))
}
@Test
fun `template literal is colored as string`() = runTest {
assertEquals(testColors.string, highlight("`hello`").colorAt(0))
}
@Test
fun `keyword inside string is not colored as keyword`() = runTest {
val result = highlight("\"const x\"")
assertEquals(testColors.string, result.colorAt(1))
assertNotEquals(testColors.keyword, result.colorAt(1))
}
@Test
fun `function declaration colors keyword and name separately`() = runTest {
val result = highlight("function myFunc() {}")
assertEquals(testColors.keyword, result.colorAt(0)) // "function"
assertEquals(testColors.functionCall, result.colorAt(9)) // "myFunc"
}
@Test
fun `class declaration colors keyword and name separately`() = runTest {
val result = highlight("class Foo {}")
assertEquals(testColors.keyword, result.colorAt(0)) // "class"
assertEquals(testColors.type, result.colorAt(6)) // "Foo"
}
@Test
fun `declaration keywords are colored as keyword`() = runTest {
for (keyword in listOf("var", "let", "const", "function", "class", "new", "async", "await")) {
assertEquals(testColors.keyword, highlight(keyword).colorAt(0), "'$keyword' should be colored as keyword")
}
// keyword.operator.expression.* in the bundle, so not the keyword key
for (word in listOf("typeof", "instanceof", "void", "delete", "in", "of", "extends")) {
assertEquals(testColors.operator, highlight(word).colorAt(0), "'$word' should be an operator")
}
}
@Test
fun `control flow keywords are colored as keyword`() = runTest {
for (keyword in listOf("if", "else", "for", "while", "return", "switch", "case", "try", "catch", "throw")) {
assertEquals(testColors.keyword, highlight(keyword).colorAt(0), "'$keyword' should be colored as keyword")
}
}
@Test
fun `keywords are bold`() = runTest {
assertEquals(FontWeight.Bold, highlight("const").spansAt(0).first().fontWeight)
}
@Test
fun `keywords before parenthesis are not colored as function calls`() = runTest {
assertEquals(testColors.keyword, highlight("if (x) {}").colorAt(0))
}
@Test
fun `call site is colored as function call`() = runTest {
// "log" starts at index 8 in "console.log()"
assertEquals(testColors.functionCall, highlight("console.log()").colorAt(8))
}
@Test
fun `language constants are colored as constant`() = runTest {
for (constant in listOf("true", "false", "null", "undefined", "NaN", "Infinity")) {
assertEquals(
testColors.constant,
highlight(constant).colorAt(0),
"'$constant' should be colored as constant",
)
}
}
@Test
fun `screaming case identifiers are colored as constant`() = runTest {
for (constant in listOf("MAX_VALUE", "API_KEY", "X")) {
assertEquals(
testColors.constant,
highlight(constant).colorAt(0),
"'$constant' should be colored as constant",
)
}
}
@Test
fun `well-known globals are colored as builtin`() = runTest {
for (builtin in listOf("console", "Math", "JSON", "Promise", "Object")) {
assertEquals(testColors.builtin, highlight(builtin).colorAt(0), "'$builtin' should be colored as builtin")
}
}
@Test
fun `all-caps globals are builtins rather than screaming case constants`() = runTest {
assertEquals(testColors.builtin, highlight("JSON").colorAt(0))
assertNotEquals(testColors.constant, highlight("JSON").colorAt(0))
}
@Test
fun `screaming case rule does not match the leading capital of a mixed-case name`() = runTest {
// "Math" is a builtin, not a "M" constant followed by "ath"
assertEquals(testColors.builtin, highlight("Math").colorAt(0))
assertEquals(testColors.builtin, highlight("Math").colorAt(1))
}
@Test
fun `object literal key is colored as property key`() = runTest {
assertEquals(testColors.propertyKey, highlight("{a: 1}").colorAt(1))
}
@Test
fun `second object literal key is colored as property key`() = runTest {
// "b" is at index 7 in "{a: 1, b: 2}" — reached via the `,` anchor
assertEquals(testColors.propertyKey, highlight("{a: 1, b: 2}").colorAt(7))
}
@Test
fun `ternary is not mistaken for an object literal key`() = runTest {
// Without the `{`/`,` anchor, "a" in `x ? a : b` would match the key pattern
assertNull(highlight("x ? a : b").colorAt(4))
}
@Test
fun `object literal braces and colons are not colored`() = runTest {
val result = highlight("{a: 1}")
assertNull(result.colorAt(0), "The `{` anchor should not be colored")
assertNull(result.colorAt(2), "The colon should not be colored")
}
@Test
fun `dollar-prefixed identifiers are not colored as keywords`() = runTest {
// `$` is an identifier character in JS, so \b would wrongly match "in" here
assertNull(highlight("\$in").colorAt(1))
assertNull(highlight("\$of").colorAt(1))
}
@Test
fun `decimal numbers are colored as number`() = runTest {
for (number in listOf("0", "42", "3.14", ".5", "1e10", "1_000")) {
assertEquals(testColors.number, highlight(number).colorAt(0), "'$number' should be colored as number")
}
}
@Test
fun `radix-prefixed numbers are colored as number`() = runTest {
for (number in listOf("0xFF", "0b1010", "0o777")) {
assertEquals(testColors.number, highlight(number).colorAt(0), "'$number' should be colored as number")
}
}
@Test
fun `bigint literals are colored as number`() = runTest {
for (number in listOf("42n", "0xFFn")) {
assertEquals(testColors.number, highlight(number).colorAt(0), "'$number' should be colored as number")
}
}
}
@@ -93,7 +93,7 @@ internal class KotlinGrammarTest {
fun `class declaration colors keyword and class name separately`() = runTest {
val result = highlight("class MyClass")
assertEquals(testColors.keyword, result.spanColorAt(0)) // "class" → keyword
assertEquals(testColors.builtin, result.spanColorAt(1)) // "MyClass" → builtin
assertEquals(testColors.type, result.spanColorAt(1)) // "MyClass" → type
}
@Test
@@ -146,9 +146,10 @@ internal class KotlinGrammarTest {
}
@Test
fun `built-in types are colored as type`() = runTest {
fun `built-in types are colored as builtin`() = runTest {
// support.type is IntelliJ's predefined-symbol key, not its class-reference key
for (type in listOf("String", "Int", "Long", "Boolean", "List", "Map", "Unit", "Any")) {
assertEquals(testColors.type, highlight(type).colorAt(0), "'$type' should be colored as type")
assertEquals(testColors.builtin, highlight(type).colorAt(0), "'$type' should be a builtin")
}
}
@@ -199,6 +200,6 @@ internal class KotlinGrammarTest {
assertEquals(testColors.keyword, spanStyles.spanColorAt(0))
assertEquals(testColors.keyword, spanStyles.spanColorAt(1))
assertEquals(testColors.builtin, spanStyles.spanColorAt(2))
assertEquals(testColors.type, spanStyles.spanColorAt(2))
}
}
@@ -0,0 +1,169 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.code.highlighting.languages
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.jetbrains.jewel.intui.code.highlighting.colorAt
import org.jetbrains.jewel.intui.code.highlighting.testColors
import org.jetbrains.jewel.intui.standalone.code.highlighting.SimpleCodeHighlighter
import org.junit.jupiter.api.Test
internal class PythonGrammarTest {
private val highlighter = SimpleCodeHighlighter(testColors)
private suspend fun highlight(code: String, language: String = "python") =
highlighter.highlight(code, language).first()
@Test
fun `name and all aliases are recognized`() = runTest {
val identifiers =
listOf("python", "py", "py3", "python3", "rpy", "pyw", "cpy", "gyp", "gypi", "pyi", "ipy", "pyt")
for (identifier in identifiers) {
assertTrue(highlight("x = 1", identifier).spanStyles.isNotEmpty(), "Alias '$identifier' not recognized")
}
}
@Test
fun `comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("# a comment").colorAt(0))
}
@Test
fun `keyword inside comment is not colored as keyword`() = runTest {
val result = highlight("# import os")
assertEquals(testColors.comment, result.colorAt(2))
assertNotEquals(testColors.keyword, result.colorAt(2))
}
@Test
fun `single-quoted string is colored as string`() = runTest {
assertEquals(testColors.string, highlight("'hello'").colorAt(0))
}
@Test
fun `triple-quoted string is colored as string`() = runTest {
assertEquals(testColors.string, highlight("\"\"\"docstring\"\"\"").colorAt(0))
}
@Test
fun `prefixed strings are colored as string`() = runTest {
for (literal in listOf("f\"hi\"", "r'hi'", "b\"hi\"", "rb\"hi\"")) {
assertEquals(testColors.string, highlight(literal).colorAt(0), "'$literal' should be colored as string")
}
}
@Test
fun `keyword inside string is not colored as keyword`() = runTest {
val result = highlight("\"import os\"")
assertEquals(testColors.string, result.colorAt(1))
assertNotEquals(testColors.keyword, result.colorAt(1))
}
@Test
fun `def declaration colors keyword and name separately`() = runTest {
val result = highlight("def my_func():")
assertEquals(testColors.keyword, result.colorAt(0)) // "def"
assertEquals(testColors.functionCall, result.colorAt(4)) // "my_func"
}
@Test
fun `class declaration colors keyword and name separately`() = runTest {
val result = highlight("class Foo:")
assertEquals(testColors.keyword, result.colorAt(0)) // "class"
assertEquals(testColors.type, result.colorAt(6)) // "Foo"
}
@Test
fun `decorator is colored as function call`() = runTest {
assertEquals(testColors.functionCall, highlight("@property").colorAt(0))
assertEquals(testColors.functionCall, highlight("@app.route").colorAt(0))
}
@Test
fun `keywords are colored as keyword`() = runTest {
val keywords = listOf("def", "class", "lambda", "return", "import", "if", "elif", "else", "for", "while")
for (keyword in keywords) {
assertEquals(testColors.keyword, highlight(keyword).colorAt(0), "'$keyword' should be colored as keyword")
}
}
@Test
fun `word operators are colored as operator`() = runTest {
// #operator scopes these as keyword.operator.logical.python, which is not the keyword key
for (word in listOf("in", "is", "not", "and", "or")) {
assertEquals(testColors.operator, highlight(word).colorAt(0), "'$word' should be an operator")
}
}
@Test
fun `language constants are colored as constant`() = runTest {
for (constant in listOf("True", "False", "None", "NotImplemented", "Ellipsis")) {
assertEquals(
testColors.constant,
highlight(constant).colorAt(0),
"'$constant' should be colored as constant",
)
}
}
@Test
fun `builtin types are colored as builtin`() = runTest {
// support.type.python maps to IntelliJ's predefined-symbol key
for (type in listOf("int", "str", "float", "bool", "dict", "list", "set", "tuple")) {
assertEquals(testColors.builtin, highlight(type).colorAt(0), "'$type' should be a builtin")
}
}
@Test
fun `builtin functions are colored as builtin`() = runTest {
for (builtin in listOf("print", "len", "range", "self", "cls", "isinstance")) {
assertEquals(testColors.builtin, highlight(builtin).colorAt(0), "'$builtin' should be colored as builtin")
}
}
@Test
fun `call site is colored as function call`() = runTest {
assertEquals(testColors.functionCall, highlight("my_func(1)").colorAt(0))
}
@Test
fun `builtins keep their color at call sites`() = runTest {
// The builtin rule is listed before functionCall, so `print(` stays a builtin
assertEquals(testColors.builtin, highlight("print(1)").colorAt(0))
}
@Test
fun `numbers are colored as number`() = runTest {
val numbers =
listOf("42", "0", "0x1F", "0xdead_beef", "0b1010", "0o755", "3.14", "1e10", "1E-5", "2j", "3.5j", "1_000")
for (number in numbers) {
assertEquals(testColors.number, highlight(number).colorAt(0), "'$number' should be colored as number")
}
}
@Test
fun `leading-dot floats are colored from the dot`() = runTest {
// #number-float's first branch. A rule anchored to a leading digit leaves the dot plain.
assertEquals(testColors.number, highlight("x = .5").colorAt(4), "the dot")
assertEquals(testColors.number, highlight("x = .5").colorAt(5), "the digit")
assertEquals(testColors.number, highlight("x = .5e2").colorAt(4), "with an exponent")
assertEquals(testColors.number, highlight("x = .5e2").colorAt(7), "through the exponent")
}
@Test
fun `underscores are allowed in the exponent`() = runTest {
// `1e1_0` is valid Python and matched nothing before the exponent allowed separators
assertEquals(testColors.number, highlight("x = 1e1_0").colorAt(4))
assertEquals(testColors.number, highlight("x = 1e1_0").colorAt(8))
}
@Test
fun `a digit inside an identifier is not a number`() = runTest {
assertNull(highlight("x = a1").colorAt(5))
assertNull(highlight("x = _1").colorAt(5))
}
}
@@ -0,0 +1,207 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.code.highlighting.languages
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.jetbrains.jewel.intui.code.highlighting.colorAt
import org.jetbrains.jewel.intui.code.highlighting.testColors
import org.jetbrains.jewel.intui.standalone.code.highlighting.SimpleCodeHighlighter
import org.junit.jupiter.api.Test
internal class SQLGrammarTest {
private val highlighter = SimpleCodeHighlighter(testColors)
private suspend fun highlight(code: String, language: String = "sql") =
highlighter.highlight(code, language).first()
@Test
fun `name and all aliases are recognized`() = runTest {
for (identifier in listOf("sql", "dsql", "ddl", "dml", "cql", "prc", "tab", "udf", "viw", "db2")) {
assertTrue(highlight("SELECT 1", identifier).spanStyles.isNotEmpty(), "Alias '$identifier' not recognized")
}
}
@Test
fun `line comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("-- a comment").colorAt(0))
}
@Test
fun `line comment ends at the newline`() = runTest {
val result = highlight("-- c\nSELECT")
assertEquals(testColors.comment, result.colorAt(0))
assertEquals(testColors.keyword, result.colorAt(5))
}
@Test
fun `line comment wins over the minus operator`() = runTest {
assertEquals(testColors.comment, highlight("SELECT 1 -- note").colorAt(9))
}
@Test
fun `block comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("/* block */").colorAt(0))
}
@Test
fun `keyword inside comment is not colored as keyword`() = runTest {
val result = highlight("-- SELECT FROM")
assertEquals(testColors.comment, result.colorAt(3))
assertNotEquals(testColors.keyword, result.colorAt(3))
}
@Test
fun `single-quoted string is colored as string`() = runTest {
assertEquals(testColors.string, highlight("'hello'").colorAt(0))
}
@Test
fun `doubled quote produces two adjacent strings`() = runTest {
// The bundle has no rule for SQL's '' escape: "'it''s'" is scanned as "'it'" followed by "'s'",
// so index 6 is still inside a string, but as the closing quote of the second one
assertEquals(testColors.string, highlight("'it''s'").colorAt(6))
}
@Test
fun `national character literal includes its N prefix`() = runTest {
assertEquals(testColors.string, highlight("N'x'").colorAt(0))
}
@Test
fun `backtick and double-quoted identifiers are colored as string`() = runTest {
assertEquals(testColors.string, highlight("`col`").colorAt(0))
assertEquals(testColors.string, highlight("\"col\"").colorAt(0))
}
@Test
fun `keywords are case-insensitive`() = runTest {
for (keyword in listOf("SELECT", "select", "Select", "SeLeCt")) {
assertEquals(testColors.keyword, highlight(keyword).colorAt(0), "'$keyword' should be colored as keyword")
}
}
@Test
fun `keywords are colored as keyword`() = runTest {
val keywords =
listOf("SELECT", "FROM", "WHERE", "INSERT", "UPDATE", "DELETE", "CREATE", "JOIN", "GROUP BY", "VALUES")
for (keyword in keywords) {
assertEquals(testColors.keyword, highlight(keyword).colorAt(0), "'$keyword' should be colored as keyword")
}
}
@Test
fun `alias and order keywords are colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("AS").colorAt(0))
assertEquals(testColors.keyword, highlight("DESC").colorAt(0))
}
@Test
fun `storage modifiers are colored as keyword`() = runTest {
val result = highlight("PRIMARY KEY")
assertEquals(testColors.keyword, result.colorAt(0))
assertEquals(testColors.keyword, result.colorAt(8))
}
@Test
fun `null is a keyword, not a constant`() = runTest {
// The bundle scopes null as keyword.other.DDL.create.II.sql; it has no constant.language rule at all
assertEquals(testColors.keyword, highlight("NULL").colorAt(0))
assertEquals(testColors.keyword, highlight("null").colorAt(0))
}
@Test
fun `true and false are not styled`() = runTest {
// Neither word appears anywhere in the bundle
assertNull(highlight("TRUE").colorAt(0))
assertNull(highlight("false").colorAt(0))
}
@Test
fun `qualified names are colored as constant`() = runTest {
// constant.other.database-name.sql and constant.other.table-name.sql
val result = highlight("myschema.mytable")
assertEquals(testColors.constant, result.colorAt(0))
assertEquals(testColors.constant, result.colorAt(9))
}
@Test
fun `operators are colored as operator`() = runTest {
assertEquals(testColors.operator, highlight("SELECT * FROM t").colorAt(7))
assertEquals(testColors.operator, highlight("x = 1").colorAt(2))
assertEquals(testColors.operator, highlight("a || b").colorAt(2))
}
@Test
fun `types are colored as keyword`() = runTest {
// storage.type.sql maps to IntelliJ's keyword key
for (type in listOf("INT", "INTEGER", "varchar", "VARCHAR2", "TIMESTAMP", "boolean")) {
assertEquals(testColors.keyword, highlight(type).colorAt(0), "'$type' should be a keyword")
}
}
@Test
fun `type length argument is colored as number`() = runTest {
val result = highlight("varchar(10)")
assertEquals(testColors.keyword, result.colorAt(0))
assertEquals(testColors.number, result.colorAt(8))
}
@Test
fun `types the bundle does not know are not styled`() = runTest {
// uuid is a PostgreSQL type; this bundle is the MSSQL one and never mentions it
assertNull(highlight("uuid").colorAt(0))
}
@Test
fun `known function names are colored as function call`() = runTest {
assertEquals(testColors.functionCall, highlight("COUNT(*)").colorAt(0))
assertEquals(testColors.functionCall, highlight("abs(1)").colorAt(0))
}
@Test
fun `keywords before parenthesis are not colored as function calls`() = runTest {
assertEquals(testColors.keyword, highlight("IN (1, 2)").colorAt(0))
}
@Test
fun `integers are colored as number`() = runTest { assertEquals(testColors.number, highlight("42").colorAt(0)) }
@Test
fun `only the integer parts of a decimal are colored as number`() = runTest {
// The bundle's only standalone numeric rule is \b\d+\b, so 3.14 is two numbers with a bare dot between
val result = highlight("3.14")
assertEquals(testColors.number, result.colorAt(0))
assertNull(result.colorAt(1))
assertEquals(testColors.number, result.colorAt(2))
}
@Test fun `exponent notation is not recognized as a number`() = runTest { assertNull(highlight("1e10").colorAt(0)) }
@Test
fun `create statement colors the keywords and the created name`() = runTest {
val result = highlight("CREATE TABLE foo")
assertEquals(testColors.keyword, result.colorAt(0))
assertEquals(testColors.keyword, result.colorAt(7))
assertEquals(testColors.functionCall, result.colorAt(13))
}
@Test
fun `drop statement colors only its keywords`() = runTest {
// Two meta.drop rules match here; the bundle lists the one without the name capture first, and it wins
val result = highlight("DROP TABLE users")
assertEquals(testColors.keyword, result.colorAt(0))
assertEquals(testColors.keyword, result.colorAt(5))
assertNull(result.colorAt(11))
}
@Test
fun `bracketed identifiers are not colored, not even reserved words`() = runTest {
assertNull(highlight("[select]").colorAt(1))
}
@Test fun `variables are not colored`() = runTest { assertNull(highlight("@count").colorAt(1)) }
}
@@ -0,0 +1,270 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.code.highlighting.languages
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.jetbrains.jewel.intui.code.highlighting.colorAt
import org.jetbrains.jewel.intui.code.highlighting.testColors
import org.jetbrains.jewel.intui.standalone.code.highlighting.SimpleCodeHighlighter
import org.junit.jupiter.api.Test
internal class ShellGrammarTest {
private val highlighter = SimpleCodeHighlighter(testColors)
private suspend fun highlight(code: String, language: String = "bash") =
highlighter.highlight(code, language).first()
@Test
fun `name and all aliases are recognized`() = runTest {
for (identifier in listOf("shellscript", "bash", "sh", "shell", "zsh", "ksh", "csh", "fish", "bats")) {
assertTrue(highlight("echo hi", identifier).spanStyles.isNotEmpty(), "Alias '$identifier' not recognized")
}
}
@Test
fun `comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("# a comment").colorAt(0))
}
@Test
fun `shebang is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("#!/usr/bin/env bash").colorAt(0))
}
@Test
fun `trailing comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("echo hi # note").colorAt(8))
}
@Test
fun `hash without leading whitespace does not open a comment`() = runTest {
// The bundle only opens a comment when the # follows whitespace or starts the line
assertNull(highlight("echo foo#bar").colorAt(8))
}
@Test
fun `single and double quoted strings are colored as string`() = runTest {
assertEquals(testColors.string, highlight("echo 'hello'").colorAt(5))
assertEquals(testColors.string, highlight("echo \"hello\"").colorAt(5))
}
@Test
fun `ansi-c and backtick spans are colored as string`() = runTest {
assertEquals(testColors.string, highlight("echo \$'a\\nb'").colorAt(5))
assertEquals(testColors.string, highlight("echo `date`").colorAt(5))
}
@Test
fun `keywords inside a string are not colored separately`() = runTest {
assertEquals(testColors.string, highlight("echo \"if then fi\"").colorAt(9))
}
@Test
fun `control flow keywords are colored as keyword`() = runTest {
val result = highlight("if true; then echo hi; fi")
assertEquals(testColors.keyword, result.colorAt(0)) // if
assertEquals(testColors.keyword, result.colorAt(9)) // then
assertEquals(testColors.keyword, result.colorAt(23)) // fi
}
@Test
fun `for in loop colors both keywords and the loop variable`() = runTest {
val result = highlight("for i in 1 2 3; do echo \$i; done")
assertEquals(testColors.keyword, result.colorAt(0)) // for
assertEquals(testColors.builtin, result.colorAt(4)) // i, variable.other.for.shell
assertEquals(testColors.keyword, result.colorAt(6)) // in
assertEquals(testColors.keyword, result.colorAt(16)) // do
assertEquals(testColors.keyword, result.colorAt(28)) // done
}
@Test
fun `while and until are colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("while read line; do :; done").colorAt(0))
assertEquals(testColors.keyword, highlight("until false; do :; done").colorAt(0))
}
@Test
fun `case and esac are colored as keyword`() = runTest {
val result = highlight("case \$x in a) ;; esac")
assertEquals(testColors.keyword, result.colorAt(0)) // case
assertEquals(testColors.keyword, result.colorAt(8)) // in
assertEquals(testColors.keyword, result.colorAt(17)) // esac
}
@Test
fun `select is colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("select x in a b; do :; done").colorAt(0))
}
@Test
fun `break continue and return are colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("while :; do break; done").colorAt(12))
assertEquals(testColors.keyword, highlight("while :; do continue; done").colorAt(12))
assertEquals(testColors.keyword, highlight("f() { return 1; }").colorAt(6))
}
@Test
fun `storage modifiers are colored as keyword`() = runTest {
for (modifier in listOf("readonly", "declare", "typeset", "export", "local")) {
assertEquals(
testColors.keyword,
highlight("$modifier x=1").colorAt(0),
"'$modifier' should be colored as keyword",
)
}
}
@Test
fun `time is colored as keyword`() = runTest { assertEquals(testColors.keyword, highlight("time ls").colorAt(0)) }
@Test
fun `function keyword and name are colored separately`() = runTest {
val result = highlight("function greet { echo hi; }")
assertEquals(testColors.keyword, result.colorAt(0))
assertEquals(testColors.functionCall, result.colorAt(9))
}
@Test
fun `posix function definition colors the name`() = runTest {
assertEquals(testColors.functionCall, highlight("greet() { echo hi; }").colorAt(0))
}
@Test
fun `shell builtins are colored as builtin`() = runTest {
for (builtin in listOf("echo", "printf", "read", "exit", "eval", "export")) {
val expected = if (builtin == "export") testColors.keyword else testColors.builtin
assertEquals(expected, highlight("$builtin x").colorAt(0), "'$builtin' has the wrong color")
}
}
@Test
fun `the no-op and dot commands are colored as builtin`() = runTest {
assertEquals(testColors.builtin, highlight(": noop").colorAt(0))
assertEquals(testColors.builtin, highlight(". ./lib.sh").colorAt(0))
}
@Test
fun `external commands are colored as function calls`() = runTest {
assertEquals(testColors.functionCall, highlight("ls -la").colorAt(0))
assertEquals(testColors.functionCall, highlight("git commit").colorAt(0))
assertEquals(testColors.functionCall, highlight("./script.sh").colorAt(0))
}
@Test
fun `commands are recognized after a pipe an ampersand and a subshell`() = runTest {
assertEquals(testColors.functionCall, highlight("ls | grep x").colorAt(7))
assertEquals(testColors.functionCall, highlight("a && b").colorAt(5))
assertEquals(testColors.builtin, highlight("(cd /tmp && ls)").colorAt(1))
assertEquals(testColors.builtin, highlight("\$(cd /tmp)").colorAt(2))
assertEquals(testColors.functionCall, highlight("if x; then ls; fi").colorAt(11))
}
@Test
fun `a bare argument is not colored`() = runTest {
assertNull(highlight("git log --oneline").colorAt(4))
assertNull(highlight("echo hello world").colorAt(10))
}
@Test
fun `variables are colored as builtin`() = runTest {
for (variable in listOf("\$HOME", "\${HOME}", "\$1", "\$@", "\$?")) {
assertEquals(
testColors.builtin,
highlight("echo $variable").colorAt(5),
"'$variable' should be colored as builtin",
)
}
}
@Test
fun `assignment colors the target and the operator`() = runTest {
val result = highlight("FOO=bar")
assertEquals(testColors.builtin, result.colorAt(0))
assertEquals(testColors.operator, result.colorAt(3))
}
@Test
fun `true and false are constants as values and builtins as commands`() = runTest {
assertEquals(testColors.constant, highlight("x=true").colorAt(2))
assertEquals(testColors.builtin, highlight("true").colorAt(0))
}
@Test
fun `options are colored as constant`() = runTest {
assertEquals(testColors.constant, highlight("set -euo pipefail").colorAt(4))
assertEquals(testColors.constant, highlight("curl --data=x").colorAt(7))
}
@Test
fun `an option is not mistaken for a builtin of the same name`() = runTest {
// `type` is in the bundle's builtin list, but `-type` here is an option
assertEquals(testColors.constant, highlight("find . -type f").colorAt(8))
}
@Test
fun `numbers are colored as number`() = runTest {
for (number in listOf("42", "0x1F", "0755", "3.14", "-1")) {
assertEquals(testColors.number, highlight("x=$number").colorAt(2), "'$number' should be a number")
}
assertEquals(testColors.number, highlight("exit 1").colorAt(5))
}
@Test
fun `arithmetic operands are numbers and the operator is left alone`() = runTest {
val result = highlight("x=\$((1 + 2))")
assertEquals(testColors.number, result.colorAt(5))
assertNull(result.colorAt(7), "#math's operators are not ported")
}
@Test
fun `redirections are colored as operator`() = runTest {
assertEquals(testColors.operator, highlight("echo hi > out.txt").colorAt(8))
assertEquals(testColors.operator, highlight("echo hi 2> out.txt").colorAt(8)) // the fd number
assertEquals(testColors.operator, highlight("echo hi 2> out.txt").colorAt(9))
assertEquals(testColors.operator, highlight("ls | grep x").colorAt(3))
assertEquals(testColors.operator, highlight("cat <<< 'x'").colorAt(4))
assertEquals(testColors.operator, highlight("cd ~").colorAt(3))
}
@Test
fun `heredoc colors the operator and the body`() = runTest {
val code = "cat <<EOF\nhello \$name\nEOF\n"
assertEquals(testColors.operator, highlight(code).colorAt(4))
assertEquals(testColors.string, highlight(code).colorAt(10))
}
@Test
fun `quoted and indented heredocs are colored too`() = runTest {
val quoted = "cat <<'EOF'\nhello\nEOF\n"
assertEquals(testColors.operator, highlight(quoted).colorAt(4))
assertEquals(testColors.string, highlight(quoted).colorAt(12))
val indented = "cat <<-EOF\n\thello\n\tEOF\n"
assertEquals(testColors.operator, highlight(indented).colorAt(4))
assertEquals(testColors.string, highlight(indented).colorAt(12))
}
@Test
fun `an unterminated heredoc does not swallow the rest of the file`() = runTest {
assertNull(highlight("cat <<EOF\nnever closed\n").colorAt(4))
}
@Test
fun `a closing brace is not mistaken for a command`() = runTest {
// The bundle's function-body block consumes it as punctuation; flat it looks like a statement start
assertNull(highlight("f() {\n echo hi\n}\n").colorAt(16))
}
@Test
fun `arithmetic parentheses do not open command position`() = runTest {
assertNull(highlight("((i++))").colorAt(2))
}
@Test
fun `builtins inside a comment are not colored`() = runTest {
assertEquals(testColors.comment, highlight("# echo hi").colorAt(2))
}
}
@@ -0,0 +1,176 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.code.highlighting.languages
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.jetbrains.jewel.intui.code.highlighting.colorAt
import org.jetbrains.jewel.intui.code.highlighting.testColors
import org.jetbrains.jewel.intui.standalone.code.highlighting.SimpleCodeHighlighter
import org.junit.jupiter.api.Test
internal class YAMLGrammarTest {
private val highlighter = SimpleCodeHighlighter(testColors)
private suspend fun highlight(code: String, language: String = "yaml") =
highlighter.highlight(code, language).first()
@Test
fun `name and all aliases are recognized`() = runTest {
for (identifier in listOf("yaml", "yml", "eyaml", "eyml", "cff", "winget")) {
assertTrue(highlight("key: 1", identifier).spanStyles.isNotEmpty(), "Alias '$identifier' not recognized")
}
}
@Test
fun `comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("# a comment").colorAt(0))
}
@Test
fun `plain key is colored as property key`() = runTest {
assertEquals(testColors.propertyKey, highlight("key: value").colorAt(0))
}
@Test
fun `indented key is colored as property key`() = runTest {
// "nested" starts at index 2; the indentation is matched but not colored
assertEquals(testColors.propertyKey, highlight("root:\n nested: 1").colorAt(8))
}
@Test
fun `key after a sequence marker is colored as property key`() = runTest {
// "name" starts at index 2 in "- name: x"
assertEquals(testColors.propertyKey, highlight("- name: x").colorAt(2))
}
@Test
fun `quoted key is colored as property key`() = runTest {
val result = highlight("\"key\": value")
assertEquals(testColors.propertyKey, result.colorAt(0))
assertNotEquals(testColors.string, result.colorAt(0), "A quoted key should not read as a string")
}
@Test
fun `flow map keys are colored as property key`() = runTest {
// The block rules are anchored to a line start, so a flow map on one line needs its own pair
assertEquals(testColors.propertyKey, highlight("a: {foo: bar}").colorAt(4), "plain")
assertEquals(testColors.propertyKey, highlight("a: {\"foo\": bar}").colorAt(4), "double-quoted")
assertEquals(testColors.propertyKey, highlight("a: {'foo': bar}").colorAt(4), "single-quoted")
assertEquals(testColors.propertyKey, highlight("{foo: bar}").colorAt(1), "at line start")
assertEquals(testColors.propertyKey, highlight("a: {foo-bar: 1}").colorAt(4), "hyphenated")
}
@Test
fun `every key in a flow map is colored, not just the first`() = runTest {
val result = highlight("a: {foo: bar, baz: qux}")
assertEquals(testColors.propertyKey, result.colorAt(4))
assertEquals(testColors.propertyKey, result.colorAt(14))
assertEquals(testColors.propertyKey, highlight("a: [{k: 1}, {k: 2}]").colorAt(5), "inside a sequence")
}
@Test
fun `a quoted flow map key is not a string`() = runTest {
val result = highlight("a: {\"foo\": bar}")
assertNotEquals(testColors.string, result.colorAt(4))
}
@Test
fun `flow map values stay unstyled`() = runTest {
assertNull(highlight("a: {foo: bar}").colorAt(9))
assertNull(highlight("a: {foo: bar, baz: qux}").colorAt(19))
}
@Test
fun `quoted value is colored as string`() = runTest {
// The opening quote is at index 5 in "key: \"value\""
assertEquals(testColors.string, highlight("key: \"value\"").colorAt(5))
}
@Test
fun `colon inside a value does not start a key`() = runTest {
// Key rules are anchored to line start, and "12" fails the end-of-scalar lookahead every numeric
// rule carries, so "12:30" stays an unstyled plain scalar
val result = highlight("time: 12:30")
assertEquals(testColors.propertyKey, result.colorAt(0))
assertNull(result.colorAt(6))
}
@Test
fun `hash without leading whitespace stays part of the key`() = runTest {
// YAML only opens a comment when the # follows whitespace
assertEquals(testColors.propertyKey, highlight("foo#bar: 1").colorAt(3))
}
@Test
fun `trailing comment is colored as comment`() = runTest {
assertEquals(testColors.comment, highlight("key: # note").colorAt(5))
}
@Test
fun `yaml directive colors the name and the version`() = runTest {
val result = highlight("%YAML 1.2")
assertEquals(testColors.keyword, result.colorAt(1)) // "YAML"
assertEquals(testColors.number, result.colorAt(6)) // "1.2"
}
@Test
fun `tag directive is colored as keyword`() = runTest {
// keyword.other.directive.tag.yaml — "TAG" starts at index 1
assertEquals(testColors.keyword, highlight("%TAG !e! tag:example.com,2000:app/").colorAt(1))
assertEquals(testColors.keyword, highlight("%TAG").colorAt(1))
}
@Test
fun `document marker is colored as keyword`() = runTest {
assertEquals(testColors.keyword, highlight("---\nkey: 1").colorAt(0))
}
@Test
fun `anchors and aliases are colored as keyword`() = runTest {
// keyword.control.flow.anchor.yaml / .alias.yaml — both start at index 6 here
assertEquals(testColors.keyword, highlight("base: &base").colorAt(6))
assertEquals(testColors.keyword, highlight("copy: *base").colorAt(6))
}
@Test
fun `tag handles are colored as keyword`() = runTest {
// The shorthand and verbatim forms both start at index 5
assertEquals(testColors.keyword, highlight("key: !!str 1").colorAt(5))
assertEquals(testColors.keyword, highlight("key: !<tag:x> 1").colorAt(5))
}
@Test
fun `language constants are colored as constant`() = runTest {
for (constant in listOf("true", "True", "TRUE", "false", "null", "NULL", "~")) {
// Each appears as a value at index 5 in "key: <constant>"
assertEquals(
testColors.constant,
highlight("key: $constant").colorAt(5),
"'$constant' should be colored as constant",
)
}
}
@Test
fun `yaml 1_1 boolean spellings are not constants`() = runTest {
// The 1.2 grammar recognizes only true/false/null/~; yes/no/on/off are 1.1 and absent from it
for (word in listOf("yes", "no", "on", "off")) {
assertNull(highlight("key: $word").colorAt(5), "'$word' should not be colored")
}
}
@Test
fun `numbers are colored as number`() = runTest {
for (number in listOf("42", "-1", "3.14", "1e10", "0x1F", "0o755", ".inf")) {
assertEquals(
testColors.number,
highlight("key: $number").colorAt(5),
"'$number' should be colored as number",
)
}
}
}
@@ -40,6 +40,8 @@
- f:getFunctionCall-0d7_KjU():J
- f:getKeyword-0d7_KjU():J
- f:getNumber-0d7_KjU():J
- f:getOperator-0d7_KjU():J
- f:getPropertyKey-0d7_KjU():J
- f:getString-0d7_KjU():J
- f:getType-0d7_KjU():J
- hashCode():I
@@ -60,6 +62,8 @@
- f:functionDeclaration(java.lang.String):org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
- f:keyword(java.lang.String):org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
- f:number(java.lang.String):org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
- f:operator(java.lang.String):org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
- f:propertyKey(java.lang.String):org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
- f:string(java.lang.String):org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
- f:type(java.lang.String):org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
- f:typeDeclaration(java.lang.String):org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
@@ -71,6 +75,8 @@
- sf:FUNCTION_CALL:org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
- sf:KEYWORD:org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
- sf:NUMBER:org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
- sf:OPERATOR:org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
- sf:PROPERTY_KEY:org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
- sf:STRING:org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
- sf:TYPE:org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
- s:getEntries():kotlin.enums.EnumEntries
@@ -137,13 +137,15 @@ package org.jetbrains.jewel.intui.standalone.code.highlighting {
}
@SuppressCompatibility @androidx.compose.runtime.Immutable @org.jetbrains.annotations.ApiStatus.Experimental @org.jetbrains.jewel.foundation.ExperimentalJewelApi public final class SyntaxHighlightColors {
ctor @KotlinOnly public SyntaxHighlightColors(androidx.compose.ui.graphics.Color keyword, androidx.compose.ui.graphics.Color type, androidx.compose.ui.graphics.Color constant, androidx.compose.ui.graphics.Color functionCall, androidx.compose.ui.graphics.Color string, androidx.compose.ui.graphics.Color comment, androidx.compose.ui.graphics.Color number, androidx.compose.ui.graphics.Color builtin);
ctor @KotlinOnly public SyntaxHighlightColors(androidx.compose.ui.graphics.Color keyword, androidx.compose.ui.graphics.Color type, androidx.compose.ui.graphics.Color constant, androidx.compose.ui.graphics.Color functionCall, androidx.compose.ui.graphics.Color string, androidx.compose.ui.graphics.Color comment, androidx.compose.ui.graphics.Color number, androidx.compose.ui.graphics.Color builtin, androidx.compose.ui.graphics.Color propertyKey, androidx.compose.ui.graphics.Color operator);
property public androidx.compose.ui.graphics.Color builtin;
property public androidx.compose.ui.graphics.Color comment;
property public androidx.compose.ui.graphics.Color constant;
property public androidx.compose.ui.graphics.Color functionCall;
property public androidx.compose.ui.graphics.Color keyword;
property public androidx.compose.ui.graphics.Color number;
property public androidx.compose.ui.graphics.Color operator;
property public androidx.compose.ui.graphics.Color propertyKey;
property public androidx.compose.ui.graphics.Color string;
property public androidx.compose.ui.graphics.Color type;
field public static final org.jetbrains.jewel.intui.standalone.code.highlighting.SyntaxHighlightColors.Companion Companion;
@@ -171,6 +173,8 @@ package org.jetbrains.jewel.intui.standalone.code.highlighting {
method public org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule functionDeclaration(@org.intellij.lang.annotations.Language("RegExp") String pattern);
method public org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule keyword(@org.intellij.lang.annotations.Language("RegExp") String pattern);
method public org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule number(@org.intellij.lang.annotations.Language("RegExp") String pattern);
method public org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule operator(@org.intellij.lang.annotations.Language("RegExp") String pattern);
method public org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule propertyKey(@org.intellij.lang.annotations.Language("RegExp") String pattern);
method public org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule string(@org.intellij.lang.annotations.Language("RegExp") String pattern);
method public org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule type(@org.intellij.lang.annotations.Language("RegExp") String pattern);
method public org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule typeDeclaration(@org.intellij.lang.annotations.Language("RegExp") String pattern);
@@ -183,6 +187,8 @@ package org.jetbrains.jewel.intui.standalone.code.highlighting {
enum_constant public static final org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType FUNCTION_CALL;
enum_constant public static final org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType KEYWORD;
enum_constant public static final org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType NUMBER;
enum_constant public static final org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType OPERATOR;
enum_constant public static final org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType PROPERTY_KEY;
enum_constant public static final org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType STRING;
enum_constant public static final org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType TYPE;
}
@@ -1,11 +1,27 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.code.highlighting
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.jewel.foundation.InternalJewelApi
import org.jetbrains.jewel.intui.standalone.code.highlighting.languages.C
import org.jetbrains.jewel.intui.standalone.code.highlighting.languages.CSS
import org.jetbrains.jewel.intui.standalone.code.highlighting.languages.HTML
import org.jetbrains.jewel.intui.standalone.code.highlighting.languages.JAVA
import org.jetbrains.jewel.intui.standalone.code.highlighting.languages.JAVASCRIPT
import org.jetbrains.jewel.intui.standalone.code.highlighting.languages.JSON
import org.jetbrains.jewel.intui.standalone.code.highlighting.languages.JSX
import org.jetbrains.jewel.intui.standalone.code.highlighting.languages.KOTLIN
import org.jetbrains.jewel.intui.standalone.code.highlighting.languages.PYTHON
import org.jetbrains.jewel.intui.standalone.code.highlighting.languages.SHELL
import org.jetbrains.jewel.intui.standalone.code.highlighting.languages.SQL
import org.jetbrains.jewel.intui.standalone.code.highlighting.languages.YAML
// Patterns are adapted from the tmLanguage grammars in plugins/textmate/lib/bundles/.
// Java's regex engine is used (see TokenRule for known PCRE/Oniguruma incompatibilities).
internal object BuiltInLanguageGrammars {
val all: List<LanguageGrammar> by lazy { listOf(KOTLIN, JAVA) }
@ApiStatus.Internal
@InternalJewelApi
public object BuiltInLanguageGrammars {
public val all: List<LanguageGrammar> by lazy {
listOf(KOTLIN, JAVA, JSON, JAVASCRIPT, JSX, PYTHON, YAML, HTML, CSS, SQL, SHELL, C)
}
}
@@ -28,8 +28,8 @@ import org.jetbrains.jewel.foundation.code.highlighting.CodeHighlighter
* [additionalGrammars]; these are searched first, so they can override built-in behavior for a given language name.
*
* Token colors are controlled by [SyntaxHighlightColors]. Use [SyntaxHighlightColors.Companion.light] and
* [SyntaxHighlightColors.Companion.dark] for palettes matching IntelliJ's Default and Darcula editor schemes, or supply
* your own. Theme changes are handled at the call site via `remember(isDark)` in `ProvideMarkdownStyling` — this class
* [SyntaxHighlightColors.Companion.dark] for palettes matching IntelliJ's Light and Dark editor schemes, or supply your
* own. Theme changes are handled at the call site via `remember(isDark)` in `ProvideMarkdownStyling` — this class
* itself is stateless and always emits a single [AnnotatedString].
*
* @param colors The token color palette to use for styling.
@@ -87,13 +87,33 @@ public class SimpleCodeHighlighter(
val spans = mutableListOf<Span>()
var i = 0
// Cache each rule's next match. Without this, rules with nothing left to match rescan to end-of-input
// at every cursor position and tokenizing goes quadratic.
//
// Safe because find() returns the leftmost match at or after the start index, so a cached match that
// still starts at or after `i` is what a fresh find would return. Re-find only once the cursor passes it.
// This would break for \G, which anchors to the search start, but ported grammars have to strip \G.
val nextMatch = arrayOfNulls<MatchResult>(grammar.rules.size)
val exhausted = BooleanArray(grammar.rules.size)
while (i < code.length) {
// Find the rule whose match starts earliest; ties broken by rule order
var bestMatch: MatchResult? = null
var bestRule: TokenRule? = null
for (rule in grammar.rules) {
val match = rule.find(code, i) ?: continue
for ((ruleIndex, rule) in grammar.rules.withIndex()) {
if (exhausted[ruleIndex]) continue
var match = nextMatch[ruleIndex]
if (match == null || match.range.first < i) {
match = rule.find(code, i)
nextMatch[ruleIndex] = match
if (match == null) {
exhausted[ruleIndex] = true
continue
}
}
if (bestMatch == null || match.range.first < bestMatch.range.first) {
bestMatch = match
bestRule = rule
@@ -12,21 +12,23 @@ import org.jetbrains.jewel.foundation.ExperimentalJewelApi
/**
* Defines the colors used to render each [TokenType] in a syntax-highlighted code block.
*
* The default light and dark palettes are based on IntelliJ IDEA's "Default" and "Darcula" editor color schemes
* respectively. Colors are intentionally not sourced from the Jewel UI theme palette — those are UI chrome colors, not
* editor token colors.
* The default light and dark palettes are based on IntelliJ IDEA's "Light" and "Dark" editor color schemes, the two the
* New UI selects by default. Colors are intentionally not sourced from the Jewel UI theme palette — those are UI chrome
* colors, not editor token colors.
*
* Use [SyntaxHighlightColors.light] and [SyntaxHighlightColors.dark] to get the built-in palettes, or construct your
* own instance to fully customize token colors.
*
* @param keyword Color for language keywords (e.g., `val`, `fun`, `class`, `if`). Rendered bold.
* @param type Color for built-in or primitive types (e.g., `String`, `Int`, `void`, `bool`).
* @param constant Color for language constants (e.g., `true`, `false`, `null`, `nil`).
* @param constant Color for language constants (e.g., `true`, `false`, `null`, `nil`). Rendered italic.
* @param functionCall Color for function and method names.
* @param string Color for string literals.
* @param comment Color for line and block comments. Rendered italic.
* @param number Color for numeric literals.
* @param builtin Color for well-known built-in functions and standard library identifiers.
* @param builtin Color for well-known built-in functions and standard library identifiers. Rendered italic.
* @param propertyKey Color for keys in data languages, e.g. the `"name"` in JSON's `"name": 1`.
* @param operator Color for operators, e.g. `+`, `==`, `&&`, `|`.
*/
@ApiStatus.Experimental
@ExperimentalJewelApi
@@ -40,17 +42,21 @@ public class SyntaxHighlightColors(
public val comment: Color,
public val number: Color,
public val builtin: Color,
public val propertyKey: Color,
public val operator: Color,
) {
internal fun styleFor(tokenType: TokenType): SpanStyle =
when (tokenType) {
TokenType.KEYWORD -> SpanStyle(color = keyword, fontWeight = FontWeight.Bold)
TokenType.TYPE -> SpanStyle(color = type)
TokenType.CONSTANT -> SpanStyle(color = constant)
TokenType.CONSTANT -> SpanStyle(color = constant, fontStyle = FontStyle.Italic)
TokenType.FUNCTION_CALL -> SpanStyle(color = functionCall)
TokenType.STRING -> SpanStyle(color = string)
TokenType.COMMENT -> SpanStyle(color = comment, fontStyle = FontStyle.Italic)
TokenType.NUMBER -> SpanStyle(color = number)
TokenType.BUILTIN -> SpanStyle(color = builtin)
TokenType.BUILTIN -> SpanStyle(color = builtin, fontStyle = FontStyle.Italic)
TokenType.PROPERTY_KEY -> SpanStyle(color = propertyKey)
TokenType.OPERATOR -> SpanStyle(color = operator)
}
override fun equals(other: Any?): Boolean {
@@ -67,6 +73,8 @@ public class SyntaxHighlightColors(
if (comment != other.comment) return false
if (number != other.number) return false
if (builtin != other.builtin) return false
if (propertyKey != other.propertyKey) return false
if (operator != other.operator) return false
return true
}
@@ -80,6 +88,8 @@ public class SyntaxHighlightColors(
result = 31 * result + comment.hashCode()
result = 31 * result + number.hashCode()
result = 31 * result + builtin.hashCode()
result = 31 * result + propertyKey.hashCode()
result = 31 * result + operator.hashCode()
return result
}
@@ -92,45 +102,55 @@ public class SyntaxHighlightColors(
"string=$string, " +
"comment=$comment, " +
"number=$number, " +
"builtin=$builtin" +
"builtin=$builtin, " +
"propertyKey=$propertyKey, " +
"operator=$operator" +
")"
/** Companion object for [SyntaxHighlightColors], holding the built-in palettes. */
public companion object {
/**
* Returns a [SyntaxHighlightColors] palette matching IntelliJ IDEA's "Default" (light) editor color scheme.
* Returns a [SyntaxHighlightColors] palette matching IntelliJ IDEA's "Light" editor color scheme, the one the
* New UI selects by default.
*
* Colors sourced from `platform/platform-resources/src/DefaultColorSchemesManager.xml`, scheme `Default`. Token
* types not explicitly defined in that scheme (e.g., function names, type names) use [Color.Unspecified], which
* means they inherit the ambient text color and appear unstyled.
* Colors sourced from `platform/platform-resources/src/themes/expUI/expUI_lightScheme.xml`, scheme `Light`,
* following its `parent_scheme="Default"` and then each key's own fallback. Token types the scheme gives no
* foreground use [Color.Unspecified], which inherits the ambient text color.
*/
public fun light(): SyntaxHighlightColors =
SyntaxHighlightColors(
keyword = Color(0xFF000080), // DEFAULT_KEYWORD: value="80" → 000080
type = Color.Unspecified, // not defined in Default scheme — inherits
constant = Color(0xFF660E7A), // DEFAULT_CONSTANT: value="660e7a"
functionCall = Color.Unspecified, // DEFAULT_FUNCTION_DECLARATION: not defined in Default — inherits
string = Color(0xFF008000), // DEFAULT_STRING: value="008000"
comment = Color(0xFF808080), // DEFAULT_LINE_COMMENT / DEFAULT_BLOCK_COMMENT: value="808080"
number = Color(0xFF0000FF), // DEFAULT_NUMBER: value="ff" → 0000ff
builtin = Color.Unspecified, // DEFAULT_PREDEFINED_SYMBOL: bold only, no color — inherits
keyword = Color(0xFF0033B3), // DEFAULT_KEYWORD: value="33b3"
type = Color.Unspecified, // DEFAULT_CLASS_REFERENCE: unset, resolves to DEFAULT_IDENTIFIER
constant = Color(0xFF871094), // DEFAULT_CONSTANT: value="871094"
functionCall = Color(0xFF00627A), // DEFAULT_FUNCTION_DECLARATION: value="627a"
string = Color(0xFF067D17), // DEFAULT_STRING: value="67d17"
comment = Color(0xFF8C8C8C), // DEFAULT_LINE_COMMENT: value="8c8c8c"
number = Color(0xFF1750EB), // DEFAULT_NUMBER: value="1750eb"
builtin = Color.Unspecified, // DEFAULT_PREDEFINED_SYMBOL: italic only, no foreground
propertyKey = Color(0xFF871094), // DEFAULT_INSTANCE_FIELD: value="871094"
operator = Color.Unspecified, // DEFAULT_OPERATION_SIGN: unset, and the key has no fallback
)
/**
* Returns a [SyntaxHighlightColors] palette matching IntelliJ IDEA's "Darcula" (dark) editor color scheme.
* Returns a [SyntaxHighlightColors] palette matching IntelliJ IDEA's "Dark" editor color scheme, the one the
* New UI selects by default. This is not Darcula, which is the older scheme it inherits from.
*
* Colors sourced from `platform/platform-resources/src/DefaultColorSchemesManager.xml`, scheme `Darcula`.
* Colors sourced from `platform/platform-resources/src/themes/expUI/expUI_darkScheme.xml`, scheme `Dark`.
*/
public fun dark(): SyntaxHighlightColors =
SyntaxHighlightColors(
keyword = Color(0xFFCC7832), // DEFAULT_KEYWORD: value="cc7832"
type = Color(0xFF769AA5), // DEFAULT_CLASS_REFERENCE: value="769aa5"
constant = Color(0xFF9876AA), // DEFAULT_CONSTANT: value="9876aa"
functionCall = Color(0xFFFFC66D), // DEFAULT_FUNCTION_DECLARATION: value="ffc66d"
string = Color(0xFF6A8759), // DEFAULT_STRING: value="6a8759"
comment = Color(0xFF808080), // DEFAULT_LINE_COMMENT / DEFAULT_BLOCK_COMMENT: value="808080"
number = Color(0xFF6897BB), // DEFAULT_NUMBER: value="6897bb"
builtin = Color(0xFF9876AA), // DEFAULT_CONSTANT (inherited by predefined symbols in Darcula)
keyword = Color(0xFFCF8E6D), // DEFAULT_KEYWORD: value="cf8e6d"
// DEFAULT_CLASS_REFERENCE is "bcbec4", which is this scheme's own TEXT foreground — deliberately
// plain. Unspecified says the same thing without pinning us to a dark surface.
type = Color.Unspecified,
constant = Color(0xFFC77DBB), // DEFAULT_CONSTANT: value="c77dbb"
functionCall = Color(0xFF56A8F5), // DEFAULT_FUNCTION_DECLARATION: value="56a8f5"
string = Color(0xFF6AAB73), // DEFAULT_STRING: value="6aab73"
comment = Color(0xFF7A7E85), // DEFAULT_LINE_COMMENT: value="7a7e85"
number = Color(0xFF2AACB8), // DEFAULT_NUMBER: value="2aacb8"
builtin = Color.Unspecified, // DEFAULT_PREDEFINED_SYMBOL: italic only, no foreground
propertyKey = Color(0xFFC77DBB), // DEFAULT_INSTANCE_FIELD: value="c77dbb"
operator = Color.Unspecified, // DEFAULT_OPERATION_SIGN: "bcbec4", the TEXT foreground again
)
}
}
@@ -20,17 +20,20 @@ import org.jetbrains.jewel.foundation.ExperimentalJewelApi
* A single rule can produce multiple spans — for example, a rule that matches `fun myFunc` can color `fun` as
* [TokenType.KEYWORD] (group 1) and `myFunc` as [TokenType.FUNCTION_CALL] (group 2) in one pass.
*
* Patterns use Java's `java.util.regex` engine, which supports lookahead (`(?=...)`) and fixed-width lookbehind
* (`(?<=...)`, `(?<!...)`). This covers the vast majority of tmLanguage-style patterns.
* Patterns use Java's `java.util.regex` engine, which covers the vast majority of tmLanguage-style patterns, including
* variable-length lookbehind.
*
* **Known limitations compared to PCRE/Oniguruma** (used by TextMate grammars):
* - **POSIX character classes** (`[[:alpha:]]`, `[[:digit:]]`, etc.) are not supported — replace with their Unicode
* equivalents (`[a-zA-Z]`, `[0-9]`, etc.).
* - **Variable-length lookbehind** is not supported — only fixed-width lookbehind (e.g., `(?<=fun )` but not
* `(?<=fun\s+)`). Rewrite as a capturing-group rule instead.
* - **Open-ended repetition** (`\d{,2}`) raises "Illegal repetition" — write `\d{0,2}`.
* - Under `(?x)`, `#` and spaces inside a character class need escaping (`[\#0\-\ +']`); Java strips them.
* - **Named backreferences** and **conditional patterns** (`(?(condition)yes|no)`) are not supported.
* - **Subroutine calls** (`\g<name>`) and **recursive patterns** are not supported.
*
* See `docs/standalone-code-highlighting.md` for the full list and for what Java accepts that you might expect it not
* to.
*
* @param pattern The regex pattern string used to match against the source code.
* @param captures A map from capture group index to [TokenType]. Groups not listed here produce no colored span.
*/
@@ -72,6 +75,25 @@ public class TokenRule(@Language("RegExp") public val pattern: String, captures:
public fun builtin(@Language("RegExp") pattern: String): TokenRule =
TokenRule(pattern, mapOf(0 to TokenType.BUILTIN))
/**
* Colors the entire match as [TokenType.PROPERTY_KEY].
*
* Use a zero-width lookahead for the trailing separator so it stays outside the match, e.g.
* `"(?:[^"\\]|\\.)*"(?=\s*:)` colors a JSON key without coloring the colon.
*/
public fun propertyKey(@Language("RegExp") pattern: String): TokenRule =
TokenRule(pattern, mapOf(0 to TokenType.PROPERTY_KEY))
/**
* Colors the entire match as [TokenType.OPERATOR].
*
* This is the `keyword.operator.*` family, which IntelliJ maps to its own `DEFAULT_OPERATION_SIGN` key rather
* than to keywords. Neither default palette gives that key a color, so operators are unstyled unless a theme
* defines one.
*/
public fun operator(@Language("RegExp") pattern: String): TokenRule =
TokenRule(pattern, mapOf(0 to TokenType.OPERATOR))
/**
* Colors **group 1** of the match as [TokenType.FUNCTION_CALL].
*
@@ -91,12 +113,12 @@ public class TokenRule(@Language("RegExp") public val pattern: String, captures:
TokenRule(pattern, mapOf(1 to TokenType.KEYWORD, 2 to TokenType.FUNCTION_CALL))
/**
* Colors **group 1** as [TokenType.KEYWORD] and **group 2** as [TokenType.BUILTIN] in a single match.
* Colors **group 1** as [TokenType.KEYWORD] and **group 2** as [TokenType.TYPE] in a single match.
*
* Use this for type declaration keywords followed by the type name, e.g. `class MyClass` or `interface Foo`.
* Group 1 should capture the keyword, group 2 the type name.
*/
public fun typeDeclaration(@Language("RegExp") pattern: String): TokenRule =
TokenRule(pattern, mapOf(1 to TokenType.KEYWORD, 2 to TokenType.BUILTIN))
TokenRule(pattern, mapOf(1 to TokenType.KEYWORD, 2 to TokenType.TYPE))
}
}
@@ -35,4 +35,10 @@ public enum class TokenType {
/** Built-in functions or well-known standard library identifiers, e.g. `println`, `len`, `fmt.Println`. */
BUILTIN,
/** Keys in data languages, e.g. the `"name"` in JSON's `"name": 1`, or a YAML/TOML key. */
PROPERTY_KEY,
/** Operators and the punctuation that carries meaning: `+`, `==`, `&&`, `|`, `->`, `<<`. */
OPERATOR,
}
@@ -0,0 +1,235 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.code.highlighting.languages
import org.jetbrains.jewel.intui.standalone.code.highlighting.LanguageGrammar
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
// Patterns ported from plugins/textmate/lib/bundles/cpp/syntaxes/c.tmLanguage.json.
//
// The function-call rule gains a leading (?<![A-Za-z0-9_]), which is ours. The bundle excludes keywords
// with a lookahead but anchors the name to nothing, so against `catch (x)` the flat rule fails at `c` and
// matches `atch (` one character right.
//
// Differences you can see:
// - Escapes and printf placeholders inside a string stay string-colored. Both rules can only fire where
// the string rule has already claimed the text, so ported flat they would only ever hit a `%d` written
// outside a string, which is a modulo.
// - `p->count` colors `count` but not `p`, and a field name away from a `.` or `->` stays plain.
// - A `#if 0` block greys out down to its first `#endif` only; the bundle tracks nesting.
// - The `0x` prefix and the `f` and `UL` suffixes are part of the number. The bundle scopes them as
// keywords, which needs a second pass over the match.
// - Parameter names in a definition stay plain. The bundle knows it is inside a function head; the guess
// it makes there is unanchored without that.
// - Operators are unstyled by default. IntelliJ maps keyword.operator to DEFAULT_OPERATION_SIGN, which
// neither default scheme colors, so they only show up if a theme defines that key.
// #storage_types, storage.type.built-in.primitive.c
private const val PRIMITIVES =
"(?-mix:(?<!\\w)(?:unsigned|signed|double|_Bool|short|float|long|void|char|bool|int)(?!\\w))"
// #storage_types, storage.type.built-in.c
private const val BUILT_IN_TYPES =
"(?-mix:(?<!\\w)(?:atomic_uint_least64_t|atomic_uint_least16_t|atomic_uint_least32_t|" +
"pthread_rwlockattr_t|atomic_uint_fast64_t|atomic_uint_fast32_t|atomic_uint_fast16_t|" +
"atomic_int_least64_t|atomic_int_least32_t|atomic_int_least16_t|atomic_uint_least8_t|" +
"atomic_uint_fast8_t|atomic_int_least8_t|atomic_int_fast16_t|pthread_mutexattr_t|" +
"atomic_int_fast32_t|atomic_int_fast64_t|atomic_int_fast8_t|pthread_condattr_t|atomic_ptrdiff_t|" +
"pthread_rwlock_t|atomic_uintptr_t|atomic_uintmax_t|atomic_intmax_t|atomic_intptr_t|" +
"atomic_char32_t|atomic_char16_t|pthread_mutex_t|pthread_cond_t|atomic_wchar_t|uint_least64_t|" +
"uint_least32_t|uint_least16_t|pthread_once_t|pthread_attr_t|int_least32_t|pthread_key_t|" +
"int_least16_t|int_least64_t|uint_least8_t|uint_fast16_t|uint_fast32_t|uint_fast64_t|" +
"atomic_ushort|atomic_ullong|atomic_size_t|int_fast16_t|int_fast64_t|uint_fast8_t|atomic_short|" +
"atomic_uchar|atomic_schar|int_least8_t|memory_order|atomic_llong|atomic_ulong|int_fast32_t|" +
"atomic_long|atomic_uint|atomic_char|int_fast8_t|suseconds_t|atomic_bool|atomic_int|_Imaginary|" +
"useconds_t|in_port_t|uintmax_t|uintmax_t|pthread_t|blksize_t|in_addr_t|uintptr_t|blkcnt_t|" +
"uint16_t|uint32_t|uint64_t|u_quad_t|_Complex|intptr_t|intmax_t|intmax_t|segsz_t|u_short|nlink_t|" +
"uint8_t|int64_t|int32_t|int16_t|fixpt_t|daddr_t|caddr_t|qaddr_t|ssize_t|clock_t|swblk_t|u_long|" +
"mode_t|int8_t|time_t|ushort|u_char|quad_t|size_t|pid_t|gid_t|uid_t|dev_t|div_t|off_t|u_int|" +
"key_t|ino_t|uint|id_t|id_t)(?!\\w))"
// #member_access, variable.other.member.c on group 5. The bundle's exclusion list keeps a type name from
// reading as a field.
private const val MEMBER_ACCESS =
"((?:[a-zA-Z_]\\w*|(?<=\\]|\\)))\\s*)(?:((?:\\.\\*|\\.))|((?:->\\*|->)))((?:[a-zA-Z_]\\w*\\s*" +
"(?:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*(\\b(?!(?:atomic_uint_least64_t|" +
"atomic_uint_least16_t|atomic_uint_least32_t|atomic_uint_least8_t|atomic_int_least16_t|" +
"atomic_uint_fast64_t|atomic_uint_fast32_t|atomic_int_least64_t|atomic_int_least32_t|" +
"pthread_rwlockattr_t|atomic_uint_fast16_t|pthread_mutexattr_t|atomic_int_fast16_t|" +
"atomic_uint_fast8_t|atomic_int_fast64_t|atomic_int_least8_t|atomic_int_fast32_t|" +
"atomic_int_fast8_t|pthread_condattr_t|atomic_uintptr_t|atomic_ptrdiff_t|pthread_rwlock_t|" +
"atomic_uintmax_t|pthread_mutex_t|atomic_intmax_t|atomic_intptr_t|atomic_char32_t|" +
"atomic_char16_t|pthread_attr_t|atomic_wchar_t|uint_least64_t|uint_least32_t|uint_least16_t|" +
"pthread_cond_t|pthread_once_t|uint_fast64_t|uint_fast16_t|atomic_size_t|uint_least8_t|" +
"int_least64_t|int_least32_t|int_least16_t|pthread_key_t|atomic_ullong|atomic_ushort|" +
"uint_fast32_t|atomic_schar|atomic_short|uint_fast8_t|int_fast64_t|int_fast32_t|int_fast16_t|" +
"atomic_ulong|atomic_llong|int_least8_t|atomic_uchar|memory_order|suseconds_t|int_fast8_t|" +
"atomic_bool|atomic_char|atomic_uint|atomic_long|atomic_int|useconds_t|_Imaginary|blksize_t|" +
"pthread_t|in_addr_t|uintptr_t|in_port_t|uintmax_t|uintmax_t|blkcnt_t|uint16_t|unsigned|" +
"_Complex|uint32_t|intptr_t|intmax_t|intmax_t|uint64_t|u_quad_t|int64_t|int32_t|ssize_t|caddr_t|" +
"clock_t|uint8_t|u_short|swblk_t|segsz_t|int16_t|fixpt_t|daddr_t|nlink_t|qaddr_t|size_t|time_t|" +
"mode_t|signed|quad_t|ushort|u_long|u_char|double|int8_t|ino_t|uid_t|pid_t|_Bool|float|dev_t|" +
"div_t|short|gid_t|off_t|u_int|key_t|id_t|uint|long|void|char|bool|id_t|int)\\b)" +
"[a-zA-Z_]\\w*\\b(?!\\())"
// #function-call-innards, entity.name.function.c. The (?<![A-Za-z0-9_]) on the name branch is ours.
private const val FUNCTION_CALL =
"(?x)\n(?!(?:while|for|do|if|else|switch|catch|enumerate|return|typeid|alignof|alignas|sizeof|" +
"[cr]?iterate|and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|typeid|xor|xor_eq|alignof|" +
"alignas)\\s*\\()\n(\n(?<![A-Za-z0-9_])(?:[A-Za-z_][A-Za-z0-9_]*+|::)++ # actual name\n|\n" +
"(?:(?<=operator)(?:[-*&<>=+!]+|\\(\\)|\\[\\]))\n)\n\\s*(\\()"
// #predefined_macros, support.constant.other.c
private const val PREDEFINED_MACROS =
"\\b(__cplusplus|__DATE__|__FILE__|__LINE__|__STDC__|__STDC_HOSTED__|__STDC_NO_COMPLEX__|" +
"__STDC_VERSION__|__STDCPP_THREADS__|__TIME__|NDEBUG|__OBJC__|__ASSEMBLER__|__ATOM__|__AVX__|" +
"__AVX2__|_CHAR_UNSIGNED|__CLR_VER|_CONTROL_FLOW_GUARD|__COUNTER__|__cplusplus_cli|" +
"__cplusplus_winrt|_CPPRTTI|_CPPUNWIND|_DEBUG|_DLL|__FUNCDNAME__|__FUNCSIG__|__FUNCTION__|" +
"_INTEGRAL_MAX_BITS|__INTELLISENSE__|_ISO_VOLATILE|_KERNEL_MODE|_M_AMD64|_M_ARM|_M_ARM_ARMV7VE|" +
"_M_ARM_FP|_M_ARM64|_M_CEE|_M_CEE_PURE|_M_CEE_SAFE|_M_FP_EXCEPT|_M_FP_FAST|_M_FP_PRECISE|" +
"_M_FP_STRICT|_M_IX86|_M_IX86_FP|_M_X64|_MANAGED|_MSC_BUILD|_MSC_EXTENSIONS|_MSC_FULL_VER|" +
"_MSC_VER|_MSVC_LANG|__MSVC_RUNTIME_CHECKS|_MT|_NATIVE_WCHAR_T_DEFINED|_OPENMP|_PREFAST|" +
"__TIMESTAMP__|_VC_NO_DEFAULTLIB|_WCHAR_T_DEFINED|_WIN32|_WIN64|_WINRT_DLL|_ATL_VER|_MFC_VER|" +
"__GFORTRAN__|__GNUC__|__GNUC_MINOR__|__GNUC_PATCHLEVEL__|__GNUG__|__STRICT_ANSI__|" +
"__BASE_FILE__|__INCLUDE_LEVEL__|__ELF__|__VERSION__|__OPTIMIZE__|__OPTIMIZE_SIZE__|" +
"__NO_INLINE__|__GNUC_STDC_INLINE__|__CHAR_UNSIGNED__|__WCHAR_UNSIGNED__|__REGISTER_PREFIX__|" +
"__SIZE_TYPE__|__PTRDIFF_TYPE__|__WCHAR_TYPE__|__WINT_TYPE__|__INTMAX_TYPE__|__UINTMAX_TYPE__|" +
"__SIG_ATOMIC_TYPE__|__INT8_TYPE__|__INT16_TYPE__|__INT32_TYPE__|__INT64_TYPE__|__UINT8_TYPE__|" +
"__UINT16_TYPE__|__UINT32_TYPE__|__UINT64_TYPE__|__CHAR_BIT__|__SCHAR_MAX__|__WCHAR_MAX__|" +
"__SHRT_MAX__|__INT_MAX__|__LONG_MAX__|__LONG_LONG_MAX__|__WINT_MAX__|__SIZE_MAX__|" +
"__PTRDIFF_MAX__|__INTMAX_MAX__|__UINTMAX_MAX__|__SIG_ATOMIC_MAX__|__INTPTR_MAX__|" +
"__UINTPTR_MAX__|__WCHAR_MIN__|__WINT_MIN__|__SIG_ATOMIC_MIN__|__SIZEOF_INT__|__SIZEOF_LONG__|" +
"__SIZEOF_LONG_LONG__|__SIZEOF_SHORT__|__SIZEOF_POINTER__|__SIZEOF_FLOAT__|__SIZEOF_DOUBLE__|" +
"__SIZEOF_LONG_DOUBLE__|__SIZEOF_SIZE_T__|__SIZEOF_WCHAR_T__|__SIZEOF_WINT_T__|" +
"__SIZEOF_PTRDIFF_T__|__BYTE_ORDER__|__ORDER_LITTLE_ENDIAN__|__ORDER_BIG_ENDIAN__|" +
"__ORDER_PDP_ENDIAN__|__FLOAT_WORD_ORDER__|__DEPRECATED|__EXCEPTIONS|__GXX_RTTI|" +
"__USING_SJLJ_EXCEPTIONS__|__GXX_EXPERIMENTAL_CXX0X__|__GXX_WEAK__|__NEXT_RUNTIME__|__LP64__|" +
"_LP64|__SSP__|__SSP_ALL__|__SSP_STRONG__|__SSP_EXPLICIT__|__SANITIZE_ADDRESS__|" +
"__SANITIZE_THREAD__|__HAVE_SPECULATION_SAFE_VALUE|__GCC_HAVE_DWARF2_CFI_ASM|__FP_FAST_FMA|" +
"__FP_FAST_FMAF|__FP_FAST_FMAL|__GCC_IEC_559|__GCC_IEC_559_COMPLEX|__NO_MATH_ERRNO__|" +
"__has_builtin|__has_feature|__has_extension|__has_cpp_attribute|__has_c_attribute|" +
"__has_attribute|__has_declspec_attribute|__is_identifier|__has_include|__has_include_next|" +
"__has_warning|__FILE_NAME__|__clang__|__clang_major__|__clang_minor__|__clang_patchlevel__|" +
"__clang_version__|__fp16|_Float16)\\b"
internal val C =
LanguageGrammar(
name = "c",
aliases = listOf("cats", "h", "h.in", "i", "idc"),
rules =
listOf(
// #comments — comment.block.c then comment.line.double-slash.c, both fused
TokenRule.comment("/\\*[\\s\\S]*?\\*/"),
TokenRule.comment("//[^\\r\\n]*"),
// #preprocessor-rule-disabled — the body of a `#if 0` becomes
// comment.block.preprocessor.if-branch.c. Fused begin to end, so nesting is not tracked.
TokenRule.comment(
"(?ms)^[\\t ]*+(#)\\s*if\\b(?=\\s*\\(*\\b0+\\b\\)*\\s*(?:$|//|/\\*)).*?^[\\t ]*+#\\s*endif\\b"
),
// #strings — string.quoted.double.c and string.quoted.single.c
TokenRule.string("\"(?:\\\\.|[^\"\\\\])*\""),
TokenRule.string("'(?:\\\\.|[^'\\\\])*'"),
// #line_continuation_character — constant.character.escape.line-continuation.c
TokenRule.constant("\\\\(?=\\n)"),
// The preprocessor directives, each the begin of its own meta.preprocessor block. The
// include path is folded in as group 3, since the bundle only reaches it from inside.
TokenRule(
"(?m)^\\s*((#)\\s*(?:include(?:_next)?|import))\\b[\\t ]*(<[^>\\r\\n]*>)?",
mapOf(1 to TokenType.KEYWORD, 3 to TokenType.STRING),
),
// entity.name.function.preprocessor.c on the macro name
TokenRule(
"((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|" +
"(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((#)\\s*define\\b)\\s+" +
"((?<!\\w)[a-zA-Z_]\\w*(?!\\w))(?:(\\()([^()\\\\]+)(\\)))?",
mapOf(5 to TokenType.KEYWORD, 7 to TokenType.FUNCTION_CALL),
),
TokenRule.keyword("(?m)^\\s*(?:#)\\s*(?:if|ifdef|ifndef|elif|else|endif)\\b"),
TokenRule.keyword("(?m)^\\s*(?:#)\\s*(?:error|warning)\\b"),
TokenRule.keyword("(?m)^\\s*(?:#)\\s*(?:line|undef|pragma)\\b"),
// #predefined_macros — support.constant.other.c
TokenRule.constant(PREDEFINED_MACROS),
// #switch_statement, #case_statement, #default_statement — keyword.control.*.c
TokenRule.keyword("(?<!\\w)switch(?!\\w)"),
TokenRule.keyword("(?<!\\w)case(?!\\w)"),
TokenRule.keyword("(?<!\\w)default(?!\\w)"),
// keyword.control.c
TokenRule.keyword("\\b(break|continue|do|else|for|goto|if|_Pragma|return|while)\\b"),
// #storage_types — the aggregate and asm words read as keywords; see the header
TokenRule.keyword("(?-mix:\\b(enum|struct|union)\\b)"),
TokenRule.keyword("\\b(?:__asm__|asm)\\b"),
// keyword.other.typedef.c, then storage.modifier.c. The \b pair is ours: the bundle's rule is
// the bare word, so `int mytypedefName` colors the substring, there and here.
TokenRule.keyword("\\btypedef\\b"),
TokenRule.keyword("\\b(const|extern|register|restrict|static|volatile|inline)\\b"),
// #storage_types — storage.type.built-in.*. IntelliJ maps the whole storage.type family to
// its keyword key, which is why `int` reads as a keyword here rather than as a type.
TokenRule.keyword(PRIMITIVES),
TokenRule.keyword(BUILT_IN_TYPES),
// constant.other.variable.mac-classic.c, then the two variable.other.readwrite.*.c rules
TokenRule.constant("\\bk[A-Z]\\w*\\b"),
TokenRule.builtin("\\bg[A-Z]\\w*\\b"),
TokenRule.builtin("\\bs[A-Z]\\w*\\b"),
// constant.language.c
TokenRule.constant("\\b(NULL|true|false|TRUE|FALSE)\\b"),
// support.constant.mac-classic.c
TokenRule.constant("\\b(noErr|kNilOptions|kInvalidID|kVariableLengthArray)\\b"),
// #member_access — variable.other.member.c is group 5; group 1 is the object, left plain.
// #block_innards runs this and the call rule ahead of $self, so both have to stay ahead of
// the operators: in `argv[i]->len` the member match starts at the `-` and would lose the tie.
TokenRule(MEMBER_ACCESS, mapOf(5 to TokenType.BUILTIN)),
// #function-call-innards and #function-innards share this pattern, so it covers both a
// call and the name in a definition
TokenRule.functionCall(FUNCTION_CALL),
// #operators — keyword.operator.*.c, in the bundle's order so `<<=` beats `<<` beats `<`
TokenRule.operator("(?<![\\w$])(sizeof)(?![\\w$])"),
TokenRule.operator("--"),
TokenRule.operator("\\+\\+"),
TokenRule.operator("%=|\\+=|-=|\\*=|(?<!\\()/="),
TokenRule.operator("&=|\\^=|<<=|>>=|\\|="),
TokenRule.operator("<<|>>"),
TokenRule.operator("!=|<=|>=|==|<|>"),
TokenRule.operator("&&|!|\\|\\|"),
TokenRule.operator("&|\\||\\^|~"),
TokenRule.operator("="),
TokenRule.operator("%|\\*|/|-|\\+"),
TokenRule.operator("\\?"),
// support.type.* — the sys, pthread, stdint and mac-classic tables, then any name in _t.
// IntelliJ maps support.type to its predefined-symbol key, which is our BUILTIN.
TokenRule.builtin(
"\\b(u_char|u_short|u_int|u_long|ushort|uint|u_quad_t|quad_t|qaddr_t|caddr_t|daddr_t|" +
"div_t|dev_t|fixpt_t|blkcnt_t|blksize_t|gid_t|in_addr_t|in_port_t|ino_t|key_t|" +
"mode_t|nlink_t|id_t|pid_t|off_t|segsz_t|swblk_t|uid_t|id_t|clock_t|size_t|" +
"ssize_t|time_t|useconds_t|suseconds_t)\\b"
),
TokenRule.builtin(
"\\b(pthread_attr_t|pthread_cond_t|pthread_condattr_t|pthread_mutex_t|" +
"pthread_mutexattr_t|pthread_once_t|pthread_rwlock_t|pthread_rwlockattr_t|" +
"pthread_t|pthread_key_t)\\b"
),
TokenRule.builtin(
"(?x) \\b\n(int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|" +
"int_least8_t\n|int_least16_t|int_least32_t|int_least64_t|uint_least8_t|" +
"uint_least16_t|uint_least32_t\n|uint_least64_t|int_fast8_t|int_fast16_t|" +
"int_fast32_t|int_fast64_t|uint_fast8_t\n|uint_fast16_t|uint_fast32_t|" +
"uint_fast64_t|intptr_t|uintptr_t|intmax_t|intmax_t\n|uintmax_t|uintmax_t)\n\\b"
),
TokenRule.builtin(
"(?x) \\b\n(AbsoluteTime|Boolean|Byte|ByteCount|ByteOffset|BytePtr|CompTimeValue|" +
"ConstLogicalAddress|ConstStrFileNameParam\n|ConstStringPtr|Duration|Fixed|" +
"FixedPtr|Float32|Float32Point|Float64|Float80|Float96|FourCharCode|Fract|" +
"FractPtr\n|Handle|ItemCount|LogicalAddress|OptionBits|OSErr|OSStatus|OSType|" +
"OSTypePtr|PhysicalAddress|ProcessSerialNumber\n|ProcessSerialNumberPtr|" +
"ProcHandle|Ptr|ResType|ResTypePtr|ShortFixed|ShortFixedPtr|SignedByte|SInt16|" +
"SInt32|SInt64\n|SInt8|Size|StrFileName|StringHandle|StringPtr|TimeBase|" +
"TimeRecord|TimeScale|TimeValue|TimeValue64|UInt16|UInt32\n|UInt64|UInt8|UniChar|" +
"UniCharCount|UniCharCountPtr|UniCharPtr|UnicodeScalarValue|UniversalProcHandle|" +
"UniversalProcPtr\n|UnsignedFixed|UnsignedFixedPtr|UnsignedWide|UTF16Char|" +
"UTF32Char|UTF8Char)\n\\b"
),
TokenRule.builtin("\\b([A-Za-z0-9_]+_t)\\b"),
// #numbers — the bundle re-parses the whole match to split prefix, digits and suffix; every
// branch of that is constant.numeric.*, so the outer match maps to NUMBER as a unit
TokenRule.number("(?<!\\w)\\.?\\d(?:(?:[0-9a-zA-Z_\\.]|')|(?<=[eEpP])[+-])*"),
),
)
@@ -0,0 +1,413 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.code.highlighting.languages
import org.jetbrains.jewel.intui.standalone.code.highlighting.LanguageGrammar
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
// Patterns ported from plugins/textmate/lib/bundles/css/syntaxes/css.tmLanguage.json.
//
// Three lookarounds are ours, not the bundle's, and each stands in for context the grammar tree supplies.
// Property names need `(?=\s*:)` or `a:hover` reads `a` as a property; tag names need `(?!\s*[};])` or
// `display: table }` reads `table` as a tag. Around 20 words are on both lists, and rule order cannot
// separate them: whichever goes first misreads the other position. Hex colors need `(?![^;{}]*\{)` or
// `#abc { }` reads the id selector as a color.
//
// Differences you can see:
// - Class and id selectors are colored as types, pseudo-classes and -elements as builtins.
// - calc() arithmetic operators, standalone `even` and `odd`, :lang() language ranges, unquoted attribute
// values, the ignore-case modifier, namespace prefixes and keyframe offsets stay plain. Each is
// unanchored and only safe inside the context the bundle reaches it from.
/** Appended to the two property-name rules; see the header. */
private const val DECLARATION = "(?=\\s*:)"
// support.type.property-name.css
private const val PROPERTY_NAMES =
"(?xi)(?<![\\w-])(?:accent-color|additive-symbols|align-content|align-items|align-self|all|animation|" +
"animation-delay|animation-direction|animation-duration|animation-fill-mode|animation-iteration-count|" +
"animation-name|animation-play-state|animation-timing-function|aspect-ratio|backdrop-filter|" +
"backface-visibility|background|background-attachment|background-blend-mode|background-clip|" +
"background-color|background-image|background-origin|background-position|background-position-[xy]|" +
"background-repeat|background-size|bleed|block-size|border|border-block-end|border-block-end-color|" +
"border-block-end-style|border-block-end-width|border-block-start|border-block-start-color|" +
"border-block-start-style|border-block-start-width|border-bottom|border-bottom-color|" +
"border-bottom-left-radius|border-bottom-right-radius|border-bottom-style|border-bottom-width|" +
"border-collapse|border-color|border-end-end-radius|border-end-start-radius|border-image|" +
"border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|" +
"border-inline-end|border-inline-end-color|border-inline-end-style|border-inline-end-width|" +
"border-inline-start|border-inline-start-color|border-inline-start-style|border-inline-start-width|" +
"border-left|border-left-color|border-left-style|border-left-width|border-radius|border-right|" +
"border-right-color|border-right-style|border-right-width|border-spacing|border-start-end-radius|" +
"border-start-start-radius|border-style|border-top|border-top-color|border-top-left-radius|" +
"border-top-right-radius|border-top-style|border-top-width|border-width|bottom|box-decoration-break|" +
"box-shadow|box-sizing|break-after|break-before|break-inside|caption-side|caret-color|clear|clip|clip-path|" +
"clip-rule|color|color-adjust|color-interpolation-filters|color-scheme|column-count|column-fill|column-gap|" +
"column-rule|column-rule-color|column-rule-style|column-rule-width|column-span|column-width|columns|" +
"contain|container|container-name|container-type|content|counter-increment|counter-reset|cursor|direction|" +
"display|empty-cells|enable-background|fallback|fill|fill-opacity|fill-rule|filter|flex|flex-basis|" +
"flex-direction|flex-flow|flex-grow|flex-shrink|flex-wrap|float|flood-color|flood-opacity|font|" +
"font-display|font-family|font-feature-settings|font-kerning|font-language-override|font-optical-sizing|" +
"font-size|font-size-adjust|font-stretch|font-style|font-synthesis|font-variant|font-variant-alternates|" +
"font-variant-caps|font-variant-east-asian|font-variant-ligatures|font-variant-numeric|" +
"font-variant-position|font-variation-settings|font-weight|gap|glyph-orientation-horizontal|" +
"glyph-orientation-vertical|grid|grid-area|grid-auto-columns|grid-auto-flow|grid-auto-rows|grid-column|" +
"grid-column-end|grid-column-gap|grid-column-start|grid-gap|grid-row|grid-row-end|grid-row-gap|" +
"grid-row-start|grid-template|grid-template-areas|grid-template-columns|grid-template-rows|" +
"hanging-punctuation|height|hyphens|image-orientation|image-rendering|image-resolution|ime-mode|" +
"initial-letter|initial-letter-align|inline-size|inset|inset-block|inset-block-end|inset-block-start|" +
"inset-inline|inset-inline-end|inset-inline-start|isolation|justify-content|justify-items|justify-self|" +
"kerning|left|letter-spacing|lighting-color|line-break|line-clamp|line-height|list-style|list-style-image|" +
"list-style-position|list-style-type|margin|margin-block|margin-block-end|margin-block-start|margin-bottom|" +
"margin-inline|margin-inline-end|margin-inline-start|margin-left|margin-right|margin-top|marker-end|" +
"marker-mid|marker-start|marks|mask|mask-border|mask-border-mode|mask-border-outset|mask-border-repeat|" +
"mask-border-slice|mask-border-source|mask-border-width|mask-clip|mask-composite|mask-image|mask-mode|" +
"mask-origin|mask-position|mask-repeat|mask-size|mask-type|max-block-size|max-height|max-inline-size|" +
"max-lines|max-width|max-zoom|min-block-size|min-height|min-inline-size|min-width|min-zoom|mix-blend-mode|" +
"negative|object-fit|object-position|offset|offset-anchor|offset-distance|offset-path|offset-position|" +
"offset-rotation|opacity|order|orientation|orphans|outline|outline-color|outline-offset|outline-style|" +
"outline-width|overflow|overflow-anchor|overflow-block|overflow-inline|overflow-wrap|overflow-[xy]|" +
"overscroll-behavior|overscroll-behavior-block|overscroll-behavior-inline|overscroll-behavior-[xy]|pad|" +
"padding|padding-block|padding-block-end|padding-block-start|padding-bottom|padding-inline|" +
"padding-inline-end|padding-inline-start|padding-left|padding-right|padding-top|page-break-after|" +
"page-break-before|page-break-inside|paint-order|perspective|perspective-origin|place-content|place-items|" +
"place-self|pointer-events|position|prefix|quotes|range|resize|right|rotate|row-gap|ruby-align|ruby-merge|" +
"ruby-position|scale|scroll-behavior|scroll-margin|scroll-margin-block|scroll-margin-block-end|" +
"scroll-margin-block-start|scroll-margin-bottom|scroll-margin-inline|scroll-margin-inline-end|" +
"scroll-margin-inline-start|scroll-margin-left|scroll-margin-right|scroll-margin-top|scroll-padding|" +
"scroll-padding-block|scroll-padding-block-end|scroll-padding-block-start|scroll-padding-bottom|" +
"scroll-padding-inline|scroll-padding-inline-end|scroll-padding-inline-start|scroll-padding-left|" +
"scroll-padding-right|scroll-padding-top|scroll-snap-align|scroll-snap-coordinate|scroll-snap-destination|" +
"scroll-snap-stop|scroll-snap-type|scrollbar-color|scrollbar-gutter|scrollbar-width|" +
"shape-image-threshold|shape-margin|shape-outside|shape-rendering|size|speak-as|src|stop-color|" +
"stop-opacity|stroke|stroke-dasharray|stroke-dashoffset|stroke-linecap|stroke-linejoin|stroke-miterlimit|" +
"stroke-opacity|stroke-width|suffix|symbols|system|tab-size|table-layout|text-align|text-align-last|" +
"text-anchor|text-combine-upright|text-decoration|text-decoration-color|text-decoration-line|" +
"text-decoration-skip|text-decoration-skip-ink|text-decoration-style|text-decoration-thickness|" +
"text-emphasis|text-emphasis-color|text-emphasis-position|text-emphasis-style|text-indent|text-justify|" +
"text-orientation|text-overflow|text-rendering|text-shadow|text-size-adjust|text-transform|" +
"text-underline-offset|text-underline-position|top|touch-action|transform|transform-box|transform-origin|" +
"transform-style|transition|transition-delay|transition-duration|transition-property|" +
"transition-timing-function|translate|unicode-bidi|unicode-range|user-select|user-zoom|vertical-align|" +
"visibility|white-space|widows|width|will-change|word-break|word-spacing|word-wrap|writing-mode|z-index|" +
"zoom|alignment-baseline|baseline-shift|clip-rule|color-interpolation|color-interpolation-filters|" +
"color-profile|color-rendering|cx|cy|dominant-baseline|enable-background|fill|fill-opacity|fill-rule|" +
"flood-color|flood-opacity|glyph-orientation-horizontal|glyph-orientation-vertical|height|kerning|" +
"lighting-color|marker-end|marker-mid|marker-start|r|rx|ry|shape-rendering|stop-color|stop-opacity|stroke|" +
"stroke-dasharray|stroke-dashoffset|stroke-linecap|stroke-linejoin|stroke-miterlimit|stroke-opacity|" +
"stroke-width|text-anchor|width|x|y|adjust|after|align|align-last|alignment|alignment-adjust|appearance|" +
"attachment|azimuth|background-break|balance|baseline|before|bidi|binding|bookmark|bookmark-label|" +
"bookmark-level|bookmark-target|border-length|bottom-color|bottom-left-radius|bottom-right-radius|" +
"bottom-style|bottom-width|box|box-align|box-direction|box-flex|box-flex-group|box-lines|box-ordinal-group|" +
"box-orient|box-pack|break|character|collapse|column|column-break-after|column-break-before|count|counter|" +
"crop|cue|cue-after|cue-before|decoration|decoration-break|delay|display-model|display-role|down|drop|" +
"drop-initial-after-adjust|drop-initial-after-align|drop-initial-before-adjust|drop-initial-before-align|" +
"drop-initial-size|drop-initial-value|duration|elevation|emphasis|family|fit|fit-position|flex-group|" +
"float-offset|gap|grid-columns|grid-rows|hanging-punctuation|header|hyphenate|hyphenate-after|" +
"hyphenate-before|hyphenate-character|hyphenate-lines|hyphenate-resource|icon|image|increment|indent|" +
"index|initial-after-adjust|initial-after-align|initial-before-adjust|initial-before-align|initial-size|" +
"initial-value|inline-box-align|iteration-count|justify|label|left-color|left-style|left-width|length|" +
"level|line|line-stacking|line-stacking-ruby|line-stacking-shift|line-stacking-strategy|lines|list|mark|" +
"mark-after|mark-before|marks|marquee|marquee-direction|marquee-play-count|marquee-speed|marquee-style|max|" +
"min|model|move-to|name|nav|nav-down|nav-index|nav-left|nav-right|nav-up|new|numeral|offset|ordinal-group|" +
"orient|origin|overflow-style|overhang|pack|page|page-policy|pause|pause-after|pause-before|phonemes|pitch|" +
"pitch-range|play-count|play-during|play-state|point|presentation|presentation-level|profile|property|" +
"punctuation|punctuation-trim|radius|rate|rendering-intent|repeat|replace|reset|resolution|resource|" +
"respond-to|rest|rest-after|rest-before|richness|right-color|right-style|right-width|role|rotation|" +
"rotation-point|rows|ruby|ruby-overhang|ruby-span|rule|rule-color|rule-style|rule-width|shadow|size|" +
"size-adjust|sizing|space|space-collapse|spacing|span|speak|speak-header|speak-numeral|speak-punctuation|" +
"speech|speech-rate|speed|stacking|stacking-ruby|stacking-shift|stacking-strategy|stress|stretch|" +
"string-set|style|style-image|style-position|style-type|target|target-name|target-new|target-position|text|" +
"text-height|text-justify|text-outline|text-replace|text-wrap|timing-function|top-color|top-left-radius|" +
"top-right-radius|top-style|top-width|trim|unicode|up|user-select|variant|voice|voice-balance|" +
"voice-duration|voice-family|voice-pitch|voice-pitch-range|voice-rate|voice-stress|voice-volume|volume|" +
"weight|white|white-space-collapse|word|wrap)(?![\\w-])"
// support.type.vendored.property-name.css, and — byte for byte the same pattern —
// support.constant.vendored.property-value.css
private const val VENDORED =
"(?<![\\w-])(?i:-(?:ah|apple|atsc|epub|hp|khtml|moz|ms|o|rim|ro|tc|wap|webkit|xv)|(?:mso|prince))-[a-zA-Z-]+"
// support.constant.property-value.css
private const val PROPERTY_VALUE_KEYWORDS =
"(?xi)(?<![\\w-])(above|absolute|active|add|additive|after-edge|alias|all|all-petite-caps|all-scroll|" +
"all-small-caps|alpha|alphabetic|alternate|alternate-reverse|always|antialiased|auto|auto-fill|auto-fit|" +
"auto-pos|available|avoid|avoid-column|avoid-page|avoid-region|backwards|balance|baseline|before-edge|" +
"below|bevel|bidi-override|blink|block|block-axis|block-start|block-end|bold|bolder|border|border-box|both|" +
"bottom|bottom-outside|break-all|break-word|bullets|butt|capitalize|caption|cell|center|central|char|" +
"circle|clip|clone|close-quote|closest-corner|closest-side|col-resize|collapse|color|color-burn|" +
"color-dodge|column|column-reverse|common-ligatures|compact|condensed|contain|content|content-box|" +
"contents|context-menu|contextual|copy|cover|crisp-edges|crispEdges|crosshair|cyclic|dark|darken|dashed|" +
"decimal|default|dense|diagonal-fractions|difference|digits|disabled|disc|discretionary-ligatures|" +
"distribute|distribute-all-lines|distribute-letter|distribute-space|dot|dotted|double|double-circle|" +
"downleft|downright|e-resize|each-line|ease|ease-in|ease-in-out|ease-out|economy|ellipse|ellipsis|embed|" +
"end|evenodd|ew-resize|exact|exclude|exclusion|expanded|extends|extra-condensed|extra-expanded|fallback|" +
"farthest-corner|farthest-side|fill|fill-available|fill-box|filled|fit-content|fixed|flat|flex|flex-end|" +
"flex-start|flip|flow|flow-root|forwards|freeze|from-image|full-width|geometricPrecision|georgian|grab|" +
"grabbing|grayscale|grid|groove|hand|hanging|hard-light|help|hidden|hide|historical-forms|" +
"historical-ligatures|horizontal|horizontal-tb|hue|icon|ideograph-alpha|ideograph-numeric|" +
"ideograph-parenthesis|ideograph-space|ideographic|inactive|infinite|inherit|initial|inline|inline-axis|" +
"inline-block|inline-end|inline-flex|inline-grid|inline-list-item|inline-start|inline-table|inset|inside|" +
"inter-character|inter-ideograph|inter-word|intersect|invert|isolate|isolate-override|italic|jis04|jis78|" +
"jis83|jis90|justify|justify-all|kannada|keep-all|landscape|large|larger|left|light|lighten|lighter|line|" +
"line-edge|line-through|linear|linearRGB|lining-nums|list-item|local|loose|lowercase|lr|lr-tb|ltr|" +
"luminance|luminosity|main-size|mandatory|manipulation|manual|margin-box|match-parent|match-source|" +
"mathematical|max-content|medium|menu|message-box|middle|min-content|miter|mixed|move|multiply|n-resize|" +
"narrower|ne-resize|nearest-neighbor|nesw-resize|newspaper|no-change|no-clip|no-close-quote|" +
"no-common-ligatures|no-contextual|no-discretionary-ligatures|no-drop|no-historical-ligatures|" +
"no-open-quote|no-repeat|none|nonzero|normal|not-allowed|nowrap|ns-resize|numbers|numeric|nw-resize|" +
"nwse-resize|oblique|oldstyle-nums|open|open-quote|optimizeLegibility|optimizeQuality|optimizeSpeed|" +
"optional|ordinal|outset|outside|over|overlay|overline|padding|padding-box|page|painted|pan-down|pan-left|" +
"pan-right|pan-up|pan-x|pan-y|paused|petite-caps|pixelated|plaintext|pointer|portrait|pre|pre-line|" +
"pre-wrap|preserve-3d|progress|progressive|proportional-nums|proportional-width|proximity|radial|recto|" +
"region|relative|remove|repeat|repeat-[xy]|reset-size|reverse|revert|revert-layer|ridge|right|rl|rl-tb|" +
"round|row|row-resize|row-reverse|row-severse|rtl|ruby|ruby-base|ruby-base-container|ruby-text|" +
"ruby-text-container|run-in|running|s-resize|saturation|scale-down|screen|scroll|scroll-position|se-resize|" +
"semi-condensed|semi-expanded|separate|sesame|show|sideways|sideways-left|sideways-lr|sideways-right|" +
"sideways-rl|simplified|slashed-zero|slice|small|small-caps|small-caption|smaller|smooth|soft-light|solid|" +
"space|space-around|space-between|space-evenly|spell-out|square|sRGB|stacked-fractions|start|static|" +
"status-bar|swap|step-end|step-start|sticky|stretch|strict|stroke|stroke-box|style|sub|subgrid|" +
"subpixel-antialiased|subtract|super|sw-resize|symbolic|table|table-caption|table-cell|table-column|" +
"table-column-group|table-footer-group|table-header-group|table-row|table-row-group|tabular-nums|tb|tb-rl|" +
"text|text-after-edge|text-before-edge|text-bottom|text-top|thick|thin|titling-caps|top|top-outside|touch|" +
"traditional|transparent|triangle|ultra-condensed|ultra-expanded|under|underline|unicase|unset|upleft|" +
"uppercase|upright|use-glyph-orientation|use-script|verso|vertical|vertical-ideographic|vertical-lr|" +
"vertical-rl|vertical-text|view-box|visible|visibleFill|visiblePainted|visibleStroke|w-resize|wait|wavy|" +
"weight|whitespace|wider|words|wrap|wrap-reverse|x|x-large|x-small|xx-large|xx-small|y|zero|zoom-in|" +
"zoom-out)(?![\\w-])"
// support.constant.property-value.list-style-type.css
private const val LIST_STYLE_TYPE_KEYWORDS =
"(?xi)(?<![\\w-])(arabic-indic|armenian|bengali|cambodian|circle|cjk-decimal|cjk-earthly-branch|" +
"cjk-heavenly-stem|cjk-ideographic|decimal|decimal-leading-zero|devanagari|disc|disclosure-closed|" +
"disclosure-open|ethiopic-halehame-am|ethiopic-halehame-ti-e[rt]|ethiopic-numeric|georgian|gujarati|" +
"gurmukhi|hangul|hangul-consonant|hebrew|hiragana|hiragana-iroha|japanese-formal|japanese-informal|kannada|" +
"katakana|katakana-iroha|khmer|korean-hangul-formal|korean-hanja-formal|korean-hanja-informal|lao|" +
"lower-alpha|lower-armenian|lower-greek|lower-latin|lower-roman|malayalam|mongolian|myanmar|oriya|persian|" +
"simp-chinese-formal|simp-chinese-informal|square|tamil|telugu|thai|tibetan|trad-chinese-formal|" +
"trad-chinese-informal|upper-alpha|upper-armenian|upper-latin|upper-roman|urdu)(?![\\w-])"
// entity.name.tag.css
private const val TAG_NAMES =
"(?m)(?xi)(?<![\\w:-])(?:a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdi|bdo|" +
"bgsound|big|blink|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|content|" +
"data|datalist|dd|del|details|dfn|dialog|dir|div|dl|dt|element|em|embed|fieldset|figcaption|figure|font|" +
"footer|form|frame|frameset|h[1-6]|head|header|hgroup|hr|html|i|iframe|image|img|input|ins|isindex|kbd|" +
"keygen|label|legend|li|link|listing|main|map|mark|marquee|math|menu|menuitem|meta|meter|multicol|nav|" +
"nextid|nobr|noembed|noframes|noscript|object|ol|optgroup|option|output|p|param|picture|plaintext|pre|" +
"progress|q|rb|rp|rt|rtc|ruby|s|samp|script|section|select|shadow|slot|small|source|spacer|span|strike|" +
"strong|style|sub|summary|sup|table|tbody|td|template|textarea|tfoot|th|thead|time|title|tr|track|tt|u|ul|" +
"var|video|wbr|xmp|altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|" +
"circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|" +
"feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|" +
"feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|" +
"fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font-face|font-face-format|" +
"font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|line|" +
"linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|" +
"polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|use|" +
"view|vkern|annotation|annotation-xml|maction|maligngroup|malignmark|math|menclose|merror|mfenced|mfrac|" +
"mglyph|mi|mlabeledtr|mlongdiv|mmultiscripts|mn|mo|mover|mpadded|mphantom|mroot|mrow|ms|mscarries|mscarry|" +
"msgroup|msline|mspace|msqrt|msrow|mstack|mstyle|msub|msubsup|msup|mtable|mtd|mtext|mtr|munder|munderover|" +
"semantics)" +
// Ours, not the bundle's: stands in for the selector context #tag-names gets from the
// grammar tree. A tag name in selector position never precedes `}` or `;`; a property value
// at the end of a block always does.
"(?!\\s*[};])" +
"(?=[+~>\\s,.\\#|){:\\[]|/\\*|\$)"
// support.constant.color.w3c-extended-color-name.css
private const val EXTENDED_COLOR_NAMES =
"(?xi)(?<![\\w-])(aliceblue|antiquewhite|aquamarine|azure|beige|bisque|blanchedalmond|blueviolet|brown|" +
"burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|" +
"darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|" +
"darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|" +
"deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|gainsboro|ghostwhite|" +
"gold|goldenrod|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|" +
"lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|" +
"lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|" +
"lightyellow|limegreen|linen|magenta|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|" +
"mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|" +
"moccasin|navajowhite|oldlace|olivedrab|orangered|orchid|palegoldenrod|palegreen|paleturquoise|" +
"palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|rebeccapurple|rosybrown|royalblue|" +
"saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|skyblue|slateblue|slategray|slategrey|snow|" +
"springgreen|steelblue|tan|thistle|tomato|transparent|turquoise|violet|wheat|whitesmoke|yellowgreen)" +
"(?![\\w-])"
// support.type.property-name.media.css + support.type.vendored.property-name.media.css
private const val MEDIA_FEATURES =
"(?m)(?xi)(?<=^|\\s|\\(|\\*/)(?:((?:min-|max-)?(?:height|width|aspect-ratio|color|color-index|monochrome|" +
"resolution)|grid|scan|orientation|display-mode|hover)|((?:min-|max-)?device-(?:height|width|" +
"aspect-ratio))|((?:[-_](?:webkit|apple|khtml|epub|moz|ms|o|xv|ah|rim|atsc|hp|tc|wap|ro)|(?:mso|prince))-" +
"[\\w-]+(?=\\s*(?:/\\*(?:[^*]|\\*[^/])*\\*/)?\\s*[:)])))(?=\\s|\$|[><:=]|\\)|/\\*)"
// constant.numeric.css, with keyword.other.unit.*.css on the trailing unit
private const val NUMERIC =
"(?xi)(?<![\\w-])[-+]?(?:[0-9]+(?:\\.[0-9]+)?|\\.[0-9]+)(?:(?<=[0-9])E[-+]?[0-9]+)?(?:(%)|(deg|grad|rad|turn|" +
"Hz|kHz|ch|cm|em|ex|fr|in|mm|mozmm|pc|pt|px|q|rem|rch|rex|rlh|ic|ric|rcap|vh|vw|vb|vi|svh|svw|svb|svi|dvh|" +
"dvw|dvb|dvi|lvh|lvw|lvb|lvi|vmax|vmin|cqw|cqi|cqh|cqb|cqmin|cqmax|dpi|dpcm|dppx|s|ms)\\b)?"
// entity.other.attribute-name.pseudo-class.css
private const val PSEUDO_CLASSES =
"(?xi)(:)(:*)(?:active|any-link|checked|default|disabled|empty|enabled|first|(?:first|last|only)-(?:child|" +
"of-type)|focus|focus-visible|focus-within|fullscreen|host|hover|in-range|indeterminate|invalid|left|link|" +
"optional|out-of-range|read-only|read-write|required|right|root|scope|target|unresolved|valid|visited)" +
"(?![\\w-]|\\s*[;}])"
// entity.other.attribute-name.pseudo-element.css
private const val PSEUDO_ELEMENTS =
"(?xi)(?:(::?)(?:after|before|first-letter|first-line|(?:-(?:ah|apple|atsc|epub|hp|khtml|moz|ms|o|rim|ro|tc|" +
"wap|webkit|xv)|(?:mso|prince))-[a-z-]+)|(::)(?:backdrop|content|grammar-error|marker|placeholder|" +
"selection|shadow|spelling-error))(?![\\w-]|\\s*[;}])"
// support.function.misc.css, the long list from #functions
private const val MISC_FUNCTIONS =
"(?xi)(?<![\\w-])(annotation|attr|blur|brightness|character-variant|clamp|contrast|counters?|cross-fade|" +
"drop-shadow|element|fit-content|format|grayscale|hue-rotate|color-mix|image-set|invert|local|max|min|" +
"minmax|opacity|ornaments|repeat|saturate|sepia|styleset|stylistic|swash|symbols|cos|sin|tan|acos|asin|" +
"atan|atan2|hypot|sqrt|pow|log|exp|abs|sign)(\\()"
// A CSS custom property: variable.css in #rule-list-innards, variable.argument.css inside var(). The two patterns
// are identical apart from the leading (?<![\w-]), which is kept.
private const val CUSTOM_PROPERTY =
"(?x)(?<![\\w-])--(?:[-a-zA-Z_]|[^\\x00-\\x7F])(?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))*"
internal val CSS =
LanguageGrammar(
name = "css",
rules =
listOf(
// comment.block.css — begin `/\*`, end `\*/`
TokenRule.comment("/\\*(?:[^*]|\\*[^/])*\\*/"),
// string.quoted.double.css / string.quoted.single.css — begin `"`, end `"|(?<!\\)(?=$|\n)`, with
// #escapes as the body
TokenRule.string("(?m)\"(?:\\\\(?:[0-9a-fA-F]{1,6}|.)|[^\"\\\\\\r\\n])*+(?:\"|(?<!\\\\)(?=\$))"),
TokenRule.string("(?m)'(?:\\\\(?:[0-9a-fA-F]{1,6}|.)|[^'\\\\\\r\\n])*+(?:'|(?<!\\\\)(?=\$))"),
// constant.character.escape.codepoint.css / .newline.css / .css
TokenRule.constant("\\\\[0-9a-fA-F]{1,6}"),
TokenRule.constant("(?m)\\\\\$\\s*"),
TokenRule.constant("\\\\."),
// constant.other.color.rgb-value.hex.css. Ours, not the bundle's: (?![^;{}]*\{) stands in for
// the declaration context #property-values gets from the tree. `#abc` is both a hex color and
// an id selector, and only a selector is followed by `{`.
TokenRule.constant("(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b(?![^;{}]*\\{)"),
// constant.other.unicode-range.css — before entity.name.tag.css, which would otherwise claim the
// `U` of `U+0025` as the HTML `<u>` element (`+` is in its lookahead set)
TokenRule.constant("(?<![\\w-])[Uu]\\+[0-9A-Fa-f?]{1,6}(?:(-)[0-9A-Fa-f]{1,6})?(?![\\w-])"),
// support.type.property-name.css and support.type.vendored.property-name.css
TokenRule.propertyKey(PROPERTY_NAMES + DECLARATION),
TokenRule.propertyKey(VENDORED + DECLARATION),
// variable.css — a custom property declaration
TokenRule.propertyKey(CUSTOM_PROPERTY),
// keyword.control.at-rule.css — the bundle's generic fallback, which subsumes every named at-rule
TokenRule.keyword("(?i)(@)[\\w-]+"),
// keyword.other.important.css
TokenRule.keyword("!\\s*important(?![\\w-])"),
// entity.name.tag.css
TokenRule.keyword(TAG_NAMES),
// constant.numeric.other.density.css, meta.ratio.css (two constant.numeric.css around a
// keyword.operator.arithmetic.css) and constant.numeric.css itself. These sit ahead of
// entity.other.attribute-name.class.css because the bundle keeps `.5` away from the class rule with
// invalid.illegal.bad-identifier.css, which we drop along with the rest of invalid.*; and ahead of
// #combinators so `+5px` is a signed number rather than a combinator and a number.
TokenRule.number("(?m)(?i)(?<=[,\\s\"]|\\*/|^)\\d+x(?=[\\s,\"')]|/\\*|\$)"),
TokenRule(
"(\\d+)\\s*(/)\\s*(\\d+)",
mapOf(1 to TokenType.NUMBER, 2 to TokenType.OPERATOR, 3 to TokenType.NUMBER),
),
TokenRule(NUMERIC, mapOf(0 to TokenType.NUMBER, 1 to TokenType.KEYWORD, 2 to TokenType.KEYWORD)),
// entity.other.attribute-name.class.css / .id.css
TokenRule.type(
"(?m)(?x)(\\.)((?:[-a-zA-Z_0-9]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+)" +
"(?=\$|[\\s,.\\#)\\[:{>+~|]|/\\*)"
),
TokenRule.type(
"(?m)(?x)(\\#)(-?(?![0-9])(?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+)" +
"(?=\$|[\\s,.\\#)\\[:{>+~|]|/\\*)"
),
// entity.other.attribute-name.css, fused with meta.attribute-selector.css's `begin: "\\["` so the
// name is only recognized inside brackets
TokenRule(
"(?x)\\[\\s*(-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+)\\s*" +
"(?=[~|^\\]\$*=]|/\\*)",
mapOf(1 to TokenType.PROPERTY_KEY),
),
// keyword.operator.pattern.css
TokenRule.operator("[~|^\$*]?="),
// entity.other.attribute-name.pseudo-class.css / .pseudo-element.css
TokenRule.builtin(PSEUDO_CLASSES),
TokenRule.builtin(PSEUDO_ELEMENTS),
// #functional-pseudo-classes. The nth rule is fused with its body so that constant.numeric.css and
// support.constant.parity.css keep the `(` … `)` context they need; the others only need their name.
TokenRule(
"(?i)((:)nth-(?:last-)?(?:child|of-type))\\(\\s*(?:([+-]?(?:\\d+n?|n)(?:\\s*[+-]\\s*\\d+)?)|" +
"(even|odd))\\s*\\)",
mapOf(1 to TokenType.BUILTIN, 3 to TokenType.NUMBER, 4 to TokenType.BUILTIN),
),
TokenRule("(?i)((:)nth-(?:last-)?(?:child|of-type))(?=\\()", mapOf(1 to TokenType.BUILTIN)),
TokenRule("(?i)((:)dir)(\\()", mapOf(1 to TokenType.BUILTIN)),
TokenRule("(?i)((:)lang)(\\()", mapOf(1 to TokenType.BUILTIN)),
TokenRule("(?i)((:)(?:not|has|matches|where|is))(\\()", mapOf(1 to TokenType.BUILTIN)),
// support.function.*.css — calc, color, gradient, misc, shape, timing-function, transform, url, var
TokenRule.functionCall("(?i)(?<![\\w-])(calc)(\\()"),
TokenRule.functionCall("(?i)(?<![\\w-])(rgba?|rgb|hsla?|hsl|hwb|lab|oklab|lch|oklch|color)(\\()"),
TokenRule.functionCall(
"(?xi)(?<![\\w-])((?:-webkit-|-moz-|-o-)?(?:repeating-)?(?:linear|radial|conic)-gradient)(\\()"
),
TokenRule.functionCall(MISC_FUNCTIONS),
TokenRule.functionCall("(?i)(?<![\\w-])(circle|ellipse|inset|polygon|rect)(\\()"),
TokenRule.functionCall("(?i)(?<![\\w-])(cubic-bezier|steps)(\\()"),
TokenRule.functionCall(
"(?xi)(?<![\\w-])((?:translate|scale|rotate)(?:[XYZ]|3D)?|matrix(?:3D)?|skew[XY]?|perspective)" +
"(\\()"
),
TokenRule.functionCall("(?i)(?<![\\w@-])(url)(\\()"),
// support.function.document-rule.css, from #document-rule's header
TokenRule.functionCall("(?i)(?<![\\w-])(url-prefix|domain|regexp)(\\()"),
TokenRule.functionCall("(?i)(?<![\\w-])(var)(\\()"),
// support.constant.property-value.css, .list-style-type.css, vendored, .font-name.css. These come
// before entity.name.tag.custom.css so hyphenated keywords are not mistaken for custom elements.
TokenRule.builtin(PROPERTY_VALUE_KEYWORDS),
TokenRule.builtin(LIST_STYLE_TYPE_KEYWORDS),
TokenRule.builtin(VENDORED),
TokenRule.builtin(
"(?<![\\w-])(?i:arial|century|comic|courier|garamond|georgia|helvetica|impact|lucida|symbol|" +
"system-ui|system|tahoma|times|trebuchet|ui-monospace|ui-rounded|ui-sans-serif|ui-serif|" +
"utopia|verdana|webdings|sans-serif|serif|monospace)(?![\\w-])"
),
// support.constant.color.w3c-standard-color-name.css / .w3c-extended-color-name.css / .current.css
TokenRule.builtin(
"(?i)(?<![\\w-])(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|" +
"silver|teal|white|yellow)(?![\\w-])"
),
TokenRule.builtin(EXTENDED_COLOR_NAMES),
TokenRule.builtin("(?i)(?<![\\w-])currentColor(?![\\w-])"),
// support.constant.media.css — group 2 is invalid.deprecated.constant.media.css and is dropped
TokenRule(
"(?m)(?xi)(?<=^|\\s|,|\\*/)(?:(all|print|screen|speech)|(aural|braille|embossed|handheld|" +
"projection|tty|tv))(?=\$|[{,\\s;]|/\\*)",
mapOf(1 to TokenType.BUILTIN),
),
TokenRule(
MEDIA_FEATURES,
mapOf(1 to TokenType.PROPERTY_KEY, 2 to TokenType.PROPERTY_KEY, 3 to TokenType.PROPERTY_KEY),
),
// support.constant.property-value.css, from #media-feature-keywords
TokenRule.builtin(
"(?m)(?xi)(?<=^|\\s|:|\\*/)(?:portrait|landscape|progressive|interlace|fullscreen|standalone|" +
"minimal-ui|browser|hover)(?=\\s|\\)|\$)"
),
// keyword.operator.logical.feature.$1.css and keyword.operator.logical.$1.media.css
TokenRule("(?m)(?i)(?<=[\\s()]|^|\\*/)(and|not|or)(?=[\\s()]|/\\*|\$)", mapOf(1 to TokenType.OPERATOR)),
TokenRule("(?m)(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|\\{|/\\*|\$)", mapOf(1 to TokenType.OPERATOR)),
// keyword.operator.comparison.css
TokenRule.operator(">=|<=|=|<|>"),
// keyword.operator.gradient.css and keyword.operator.shape.css
TokenRule("(?i)(?<![\\w-])(from|to|at|in|hue)(?![\\w-])", mapOf(1 to TokenType.OPERATOR)),
TokenRule("(?m)(?i)(?<=\\s|^|\\*/)(at|round)(?=\\s|/\\*|\$)", mapOf(1 to TokenType.OPERATOR)),
// entity.name.tag.custom.css
TokenRule.keyword("(?x)(?<![@\\w-])(?=[a-z]\\w*-)(?:(?![A-Z])[\\w-])+(?![(\\w-])"),
// keyword.operator.combinator.css and entity.name.tag.wildcard.css
TokenRule.operator(">>|>|\\+|~"),
TokenRule.keyword("\\*"),
),
)
@@ -0,0 +1,169 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.code.highlighting.languages
import org.jetbrains.jewel.intui.standalone.code.highlighting.LanguageGrammar
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
// Patterns ported from plugins/textmate/lib/bundles/html/syntaxes/html.tmLanguage.json.
//
// The attribute-name rules gain a trailing `(?=\s*=)`, which is ours. #attribute is reachable only from
// inside a tag, so the bundle can list bare attribute names; flat they match anywhere, and HTML5 names are
// ordinary English words (`for`, `open`, `title`, `value`, `size`), so prose lit up as property keys.
//
// Differences you can see:
// - <script> and <style> bodies are not highlighted as JS and CSS. Nesting one grammar inside another is
// beyond the rule engine. The tag names themselves are colored.
// - Valueless attributes (`required`, `checked`, `disabled`) stay plain. That is the cost of the guard.
// - `html` in `<!DOCTYPE html>` and unquoted attribute values stay plain. Both bundle patterns match any
// run of non-space text, so flat they would color every word in the document.
// #entities, constant.character.entity.named.$2.html. Kept verbatim, including the bundle's (?x) mode and
// its own line breaks and tabs; it is a full HTML named-entity table with 912 capture groups, none of which
// this grammar reads (captures 1 and 912 are punctuation.definition.entity.html, and the `name` scope covers
// the whole match).
private const val NAMED_ENTITIES =
"""(?x)
(&) (?=[a-zA-Z])
(
(a(s(ymp(eq)?|cr|t)|n(d(slope|d|v|and)?|g(s(t|ph)|zarr|e|le|rt(vb(d)?)?|msd(a(h|c|d|e|f|a|g|b))?)?)|c(y|irc|d|ute|E)?|tilde|o(pf|gon)|uml|p(id|os|prox(eq)?|e|E|acir)?|elig|f(r)?|w(conint|int)|l(pha|e(ph|fsym))|acute|ring|grave|m(p|a(cr|lg))|breve)|A(s(sign|cr)|nd|MP|c(y|irc)|tilde|o(pf|gon)|uml|pplyFunction|fr|Elig|lpha|acute|ring|grave|macr|breve))
| (B(scr|cy|opf|umpeq|e(cause|ta|rnoullis)|fr|a(ckslash|r(v|wed))|reve)|b(s(cr|im(e)?|ol(hsub|b)?|emi)|n(ot|e(quiv)?)|c(y|ong)|ig(s(tar|qcup)|c(irc|up|ap)|triangle(down|up)|o(times|dot|plus)|uplus|vee|wedge)|o(t(tom)?|pf|wtie|x(h(d|u|D|U)?|times|H(d|u|D|U)?|d(R|l|r|L)|u(R|l|r|L)|plus|D(R|l|r|L)|v(R|h|H|l|r|L)?|U(R|l|r|L)|V(R|h|H|l|r|L)?|minus|box))|Not|dquo|u(ll(et)?|mp(e(q)?|E)?)|prime|e(caus(e)?|t(h|ween|a)|psi|rnou|mptyv)|karow|fr|l(ock|k(1(2|4)|34)|a(nk|ck(square|triangle(down|left|right)?|lozenge)))|a(ck(sim(eq)?|cong|prime|epsilon)|r(vee|wed(ge)?))|r(eve|vbar)|brk(tbrk)?))
| (c(s(cr|u(p(e)?|b(e)?))|h(cy|i|eck(mark)?)|ylcty|c(irc|ups(sm)?|edil|a(ps|ron))|tdot|ir(scir|c(eq|le(d(R|circ|S|dash|ast)|arrow(left|right)))?|e|fnint|E|mid)?|o(n(int|g(dot)?)|p(y(sr)?|f|rod)|lon(e(q)?)?|m(p(fn|le(xes|ment))?|ma(t)?))|dot|u(darr(l|r)|p(s|c(up|ap)|or|dot|brcap)?|e(sc|pr)|vee|wed|larr(p)?|r(vearrow(left|right)|ly(eq(succ|prec)|vee|wedge)|arr(m)?|ren))|e(nt(erdot)?|dil|mptyv)|fr|w(conint|int)|lubs(uit)?|a(cute|p(s|c(up|ap)|dot|and|brcup)?|r(on|et))|r(oss|arr))|C(scr|hi|c(irc|onint|edil|aron)|ircle(Minus|Times|Dot|Plus)|Hcy|o(n(tourIntegral|int|gruent)|unterClockwiseContourIntegral|p(f|roduct)|lon(e)?)|dot|up(Cap)?|OPY|e(nterDot|dilla)|fr|lo(seCurly(DoubleQuote|Quote)|ckwiseContourIntegral)|a(yleys|cute|p(italDifferentialD)?)|ross))
| (d(s(c(y|r)|trok|ol)|har(l|r)|c(y|aron)|t(dot|ri(f)?)|i(sin|e|v(ide(ontimes)?|onx)?|am(s|ond(suit)?)?|gamma)|Har|z(cy|igrarr)|o(t(square|plus|eq(dot)?|minus)?|ublebarwedge|pf|wn(harpoon(left|right)|downarrows|arrow)|llar)|d(otseq|a(rr|gger))?|u(har|arr)|jcy|e(lta|g|mptyv)|f(isht|r)|wangle|lc(orn|rop)|a(sh(v)?|leth|rr|gger)|r(c(orn|rop)|bkarow)|b(karow|lac)|Arr)|D(s(cr|trok)|c(y|aron)|Scy|i(fferentialD|a(critical(Grave|Tilde|Do(t|ubleAcute)|Acute)|mond))|o(t(Dot|Equal)?|uble(Right(Tee|Arrow)|ContourIntegral|Do(t|wnArrow)|Up(DownArrow|Arrow)|VerticalBar|L(ong(RightArrow|Left(RightArrow|Arrow))|eft(RightArrow|Tee|Arrow)))|pf|wn(Right(TeeVector|Vector(Bar)?)|Breve|Tee(Arrow)?|arrow|Left(RightVector|TeeVector|Vector(Bar)?)|Arrow(Bar|UpArrow)?))|Zcy|el(ta)?|D(otrahd)?|Jcy|fr|a(shv|rr|gger)))
| (e(s(cr|im|dot)|n(sp|g)|c(y|ir(c)?|olon|aron)|t(h|a)|o(pf|gon)|dot|u(ro|ml)|p(si(v|lon)?|lus|ar(sl)?)|e|D(ot|Dot)|q(s(im|lant(less|gtr))|c(irc|olon)|u(iv(DD)?|est|als)|vparsl)|f(Dot|r)|l(s(dot)?|inters|l)?|a(ster|cute)|r(Dot|arr)|g(s(dot)?|rave)?|x(cl|ist|p(onentiale|ectation))|m(sp(1(3|4))?|pty(set|v)?|acr))|E(s(cr|im)|c(y|irc|aron)|ta|o(pf|gon)|NG|dot|uml|TH|psilon|qu(ilibrium|al(Tilde)?)|fr|lement|acute|grave|x(ists|ponentialE)|m(pty(SmallSquare|VerySmallSquare)|acr)))
| (f(scr|nof|cy|ilig|o(pf|r(k(v)?|all))|jlig|partint|emale|f(ilig|l(ig|lig)|r)|l(tns|lig|at)|allingdotseq|r(own|a(sl|c(1(2|8|3|4|5|6)|78|2(3|5)|3(8|4|5)|45|5(8|6)))))|F(scr|cy|illed(SmallSquare|VerySmallSquare)|o(uriertrf|pf|rAll)|fr))
| (G(scr|c(y|irc|edil)|t|opf|dot|T|Jcy|fr|amma(d)?|reater(Greater|SlantEqual|Tilde|Equal(Less)?|FullEqual|Less)|g|breve)|g(s(cr|im(e|l)?)|n(sim|e(q(q)?)?|E|ap(prox)?)|c(y|irc)|t(c(c|ir)|dot|quest|lPar|r(sim|dot|eq(qless|less)|less|a(pprox|rr)))?|imel|opf|dot|jcy|e(s(cc|dot(o(l)?)?|l(es)?)?|q(slant|q)?|l)?|v(nE|ertneqq)|fr|E(l)?|l(j|E|a)?|a(cute|p|mma(d)?)|rave|g(g)?|breve))
| (h(s(cr|trok|lash)|y(phen|bull)|circ|o(ok(leftarrow|rightarrow)|pf|arr|rbar|mtht)|e(llip|arts(uit)?|rcon)|ks(earow|warow)|fr|a(irsp|lf|r(dcy|r(cir|w)?)|milt)|bar|Arr)|H(s(cr|trok)|circ|ilbertSpace|o(pf|rizontalLine)|ump(DownHump|Equal)|fr|a(cek|t)|ARDcy))
| (i(s(cr|in(s(v)?|dot|v|E)?)|n(care|t(cal|prod|e(rcal|gers)|larhk)?|odot|fin(tie)?)?|c(y|irc)?|t(ilde)?|i(nfin|i(nt|int)|ota)?|o(cy|ta|pf|gon)|u(kcy|ml)|jlig|prod|e(cy|xcl)|quest|f(f|r)|acute|grave|m(of|ped|a(cr|th|g(part|e|line))))|I(scr|n(t(e(rsection|gral))?|visible(Comma|Times))|c(y|irc)|tilde|o(ta|pf|gon)|dot|u(kcy|ml)|Ocy|Jlig|fr|Ecy|acute|grave|m(plies|a(cr|ginaryI))?))
| (j(s(cr|ercy)|c(y|irc)|opf|ukcy|fr|math)|J(s(cr|ercy)|c(y|irc)|opf|ukcy|fr))
| (k(scr|hcy|c(y|edil)|opf|jcy|fr|appa(v)?|green)|K(scr|c(y|edil)|Hcy|opf|Jcy|fr|appa))
| (l(s(h|cr|trok|im(e|g)?|q(uo(r)?|b)|aquo)|h(ar(d|u(l)?)|blk)|n(sim|e(q(q)?)?|E|ap(prox)?)|c(y|ub|e(il|dil)|aron)|Barr|t(hree|c(c|ir)|imes|dot|quest|larr|r(i(e|f)?|Par))?|Har|o(ng(left(arrow|rightarrow)|rightarrow|mapsto)|times|z(enge|f)?|oparrow(left|right)|p(f|lus|ar)|w(ast|bar)|a(ng|rr)|brk)|d(sh|ca|quo(r)?|r(dhar|ushar))|ur(dshar|uhar)|jcy|par(lt)?|e(s(s(sim|dot|eq(qgtr|gtr)|approx|gtr)|cc|dot(o(r)?)?|g(es)?)?|q(slant|q)?|ft(harpoon(down|up)|threetimes|leftarrows|arrow(tail)?|right(squigarrow|harpoons|arrow(s)?))|g)?|v(nE|ertneqq)|f(isht|loor|r)|E(g)?|l(hard|corner|tri|arr)?|a(ng(d|le)?|cute|t(e(s)?|ail)?|p|emptyv|quo|rr(sim|hk|tl|pl|fs|lp|b(fs)?)?|gran|mbda)|r(har(d)?|corner|tri|arr|m)|g(E)?|m(idot|oust(ache)?)|b(arr|r(k(sl(d|u)|e)|ac(e|k))|brk)|A(tail|arr|rr))|L(s(h|cr|trok)|c(y|edil|aron)|t|o(ng(RightArrow|left(arrow|rightarrow)|rightarrow|Left(RightArrow|Arrow))|pf|wer(RightArrow|LeftArrow))|T|e(ss(Greater|SlantEqual|Tilde|EqualGreater|FullEqual|Less)|ft(Right(Vector|Arrow)|Ceiling|T(ee(Vector|Arrow)?|riangle(Bar|Equal)?)|Do(ubleBracket|wn(TeeVector|Vector(Bar)?))|Up(TeeVector|DownVector|Vector(Bar)?)|Vector(Bar)?|arrow|rightarrow|Floor|A(ngleBracket|rrow(RightArrow|Bar)?)))|Jcy|fr|l(eftarrow)?|a(ng|cute|placetrf|rr|mbda)|midot))
| (M(scr|cy|inusPlus|opf|u|e(diumSpace|llintrf)|fr|ap)|m(s(cr|tpos)|ho|nplus|c(y|omma)|i(nus(d(u)?|b)?|cro|d(cir|dot|ast)?)|o(dels|pf)|dash|u(ltimap|map)?|p|easuredangle|DDot|fr|l(cp|dr)|a(cr|p(sto(down|up|left)?)?|l(t(ese)?|e)|rker)))
| (n(s(hort(parallel|mid)|c(cue|e|r)?|im(e(q)?)?|u(cc(eq)?|p(set(eq(q)?)?|e|E)?|b(set(eq(q)?)?|e|E)?)|par|qsu(pe|be)|mid)|Rightarrow|h(par|arr|Arr)|G(t(v)?|g)|c(y|ong(dot)?|up|edil|a(p|ron))|t(ilde|lg|riangle(left(eq)?|right(eq)?)|gl)|i(s(d)?|v)?|o(t(ni(v(c|a|b))?|in(dot|v(c|a|b)|E)?)?|pf)|dash|u(m(sp|ero)?)?|jcy|p(olint|ar(sl|t|allel)?|r(cue|e(c(eq)?)?)?)|e(s(im|ear)|dot|quiv|ar(hk|r(ow)?)|xist(s)?|Arr)?|v(sim|infin|Harr|dash|Dash|l(t(rie)?|e|Arr)|ap|r(trie|Arr)|g(t|e))|fr|w(near|ar(hk|r(ow)?)|Arr)|V(dash|Dash)|l(sim|t(ri(e)?)?|dr|e(s(s)?|q(slant|q)?|ft(arrow|rightarrow))?|E|arr|Arr)|a(ng|cute|tur(al(s)?)?|p(id|os|prox|E)?|bla)|r(tri(e)?|ightarrow|arr(c|w)?|Arr)|g(sim|t(r)?|e(s|q(slant|q)?)?|E)|mid|L(t(v)?|eft(arrow|rightarrow)|l)|b(sp|ump(e)?))|N(scr|c(y|edil|aron)|tilde|o(nBreakingSpace|Break|t(R(ightTriangle(Bar|Equal)?|everseElement)|Greater(Greater|SlantEqual|Tilde|Equal|FullEqual|Less)?|S(u(cceeds(SlantEqual|Tilde|Equal)?|perset(Equal)?|bset(Equal)?)|quareSu(perset(Equal)?|bset(Equal)?))|Hump(DownHump|Equal)|Nested(GreaterGreater|LessLess)|C(ongruent|upCap)|Tilde(Tilde|Equal|FullEqual)?|DoubleVerticalBar|Precedes(SlantEqual|Equal)?|E(qual(Tilde)?|lement|xists)|VerticalBar|Le(ss(Greater|SlantEqual|Tilde|Equal|Less)?|ftTriangle(Bar|Equal)?))?|pf)|u|e(sted(GreaterGreater|LessLess)|wLine|gative(MediumSpace|Thi(nSpace|ckSpace)|VeryThinSpace))|Jcy|fr|acute))
| (o(s(cr|ol|lash)|h(m|bar)|c(y|ir(c)?)|ti(lde|mes(as)?)|S|int|opf|d(sold|iv|ot|ash|blac)|uml|p(erp|lus|ar)|elig|vbar|f(cir|r)|l(c(ir|ross)|t|ine|arr)|a(st|cute)|r(slope|igof|or|d(er(of)?|f|m)?|v|arr)?|g(t|on|rave)|m(i(nus|cron|d)|ega|acr))|O(s(cr|lash)|c(y|irc)|ti(lde|mes)|opf|dblac|uml|penCurly(DoubleQuote|Quote)|ver(B(ar|rac(e|ket))|Parenthesis)|fr|Elig|acute|r|grave|m(icron|ega|acr)))
| (p(s(cr|i)|h(i(v)?|one|mmat)|cy|i(tchfork|v)?|o(intint|und|pf)|uncsp|er(cnt|tenk|iod|p|mil)|fr|l(us(sim|cir|two|d(o|u)|e|acir|mn|b)?|an(ck(h)?|kv))|ar(s(im|l)|t|a(llel)?)?|r(sim|n(sim|E|ap)|cue|ime(s)?|o(d|p(to)?|f(surf|line|alar))|urel|e(c(sim|n(sim|eqq|approx)|curlyeq|eq|approx)?)?|E|ap)?|m)|P(s(cr|i)|hi|cy|i|o(incareplane|pf)|fr|lusMinus|artialD|r(ime|o(duct|portion(al)?)|ecedes(SlantEqual|Tilde|Equal)?)?))
| (q(scr|int|opf|u(ot|est(eq)?|at(int|ernions))|prime|fr)|Q(scr|opf|UOT|fr))
| (R(s(h|cr)|ho|c(y|edil|aron)|Barr|ight(Ceiling|T(ee(Vector|Arrow)?|riangle(Bar|Equal)?)|Do(ubleBracket|wn(TeeVector|Vector(Bar)?))|Up(TeeVector|DownVector|Vector(Bar)?)|Vector(Bar)?|arrow|Floor|A(ngleBracket|rrow(Bar|LeftArrow)?))|o(undImplies|pf)|uleDelayed|e(verse(UpEquilibrium|E(quilibrium|lement)))?|fr|EG|a(ng|cute|rr(tl)?)|rightarrow)|r(s(h|cr|q(uo(r)?|b)|aquo)|h(o(v)?|ar(d|u(l)?))|nmid|c(y|ub|e(il|dil)|aron)|Barr|t(hree|imes|ri(e|f|ltri)?)|i(singdotseq|ng|ght(squigarrow|harpoon(down|up)|threetimes|left(harpoons|arrows)|arrow(tail)?|rightarrows))|Har|o(times|p(f|lus|ar)|a(ng|rr)|brk)|d(sh|ca|quo(r)?|ldhar)|uluhar|p(polint|ar(gt)?)|e(ct|al(s|ine|part)?|g)|f(isht|loor|r)|l(har|arr|m)|a(ng(d|e|le)?|c(ute|e)|t(io(nals)?|ail)|dic|emptyv|quo|rr(sim|hk|c|tl|pl|fs|w|lp|ap|b(fs)?)?)|rarr|x|moust(ache)?|b(arr|r(k(sl(d|u)|e)|ac(e|k))|brk)|A(tail|arr|rr)))
| (s(s(cr|tarf|etmn|mile)|h(y|c(hcy|y)|ort(parallel|mid)|arp)|c(sim|y|n(sim|E|ap)|cue|irc|polint|e(dil)?|E|a(p|ron))?|t(ar(f)?|r(ns|aight(phi|epsilon)))|i(gma(v|f)?|m(ne|dot|plus|e(q)?|l(E)?|rarr|g(E)?)?)|zlig|o(pf|ftcy|l(b(ar)?)?)|dot(e|b)?|u(ng|cc(sim|n(sim|eqq|approx)|curlyeq|eq|approx)?|p(s(im|u(p|b)|et(neq(q)?|eq(q)?)?)|hs(ol|ub)|1|n(e|E)|2|d(sub|ot)|3|plus|e(dot)?|E|larr|mult)?|m|b(s(im|u(p|b)|et(neq(q)?|eq(q)?)?)|n(e|E)|dot|plus|e(dot)?|E|rarr|mult)?)|pa(des(uit)?|r)|e(swar|ct|tm(n|inus)|ar(hk|r(ow)?)|xt|mi|Arr)|q(su(p(set(eq)?|e)?|b(set(eq)?|e)?)|c(up(s)?|ap(s)?)|u(f|ar(e|f))?)|fr(own)?|w(nwar|ar(hk|r(ow)?)|Arr)|larr|acute|rarr|m(t(e(s)?)?|i(d|le)|eparsl|a(shp|llsetminus))|bquo)|S(scr|hort(RightArrow|DownArrow|UpArrow|LeftArrow)|c(y|irc|edil|aron)?|tar|igma|H(cy|CHcy)|opf|u(c(hThat|ceeds(SlantEqual|Tilde|Equal)?)|p(set|erset(Equal)?)?|m|b(set(Equal)?)?)|OFTcy|q(uare(Su(perset(Equal)?|bset(Equal)?)|Intersection|Union)?|rt)|fr|acute|mallCircle))
| (t(s(hcy|c(y|r)|trok)|h(i(nsp|ck(sim|approx))|orn|e(ta(sym|v)?|re(4|fore))|k(sim|ap))|c(y|edil|aron)|i(nt|lde|mes(d|b(ar)?)?)|o(sa|p(cir|f(ork)?|bot)?|ea)|dot|prime|elrec|fr|w(ixt|ohead(leftarrow|rightarrow))|a(u|rget)|r(i(sb|time|dot|plus|e|angle(down|q|left(eq)?|right(eq)?)?|minus)|pezium|ade)|brk)|T(s(cr|trok)|RADE|h(i(nSpace|ckSpace)|e(ta|refore))|c(y|edil|aron)|S(cy|Hcy)|ilde(Tilde|Equal|FullEqual)?|HORN|opf|fr|a(u|b)|ripleDot))
| (u(scr|h(ar(l|r)|blk)|c(y|irc)|t(ilde|dot|ri(f)?)|Har|o(pf|gon)|d(har|arr|blac)|u(arr|ml)|p(si(h|lon)?|harpoon(left|right)|downarrow|uparrows|lus|arrow)|f(isht|r)|wangle|l(c(orn(er)?|rop)|tri)|a(cute|rr)|r(c(orn(er)?|rop)|tri|ing)|grave|m(l|acr)|br(cy|eve)|Arr)|U(scr|n(ion(Plus)?|der(B(ar|rac(e|ket))|Parenthesis))|c(y|irc)|tilde|o(pf|gon)|dblac|uml|p(si(lon)?|downarrow|Tee(Arrow)?|per(RightArrow|LeftArrow)|DownArrow|Equilibrium|arrow|Arrow(Bar|DownArrow)?)|fr|a(cute|rr(ocir)?)|ring|grave|macr|br(cy|eve)))
| (v(s(cr|u(pn(e|E)|bn(e|E)))|nsu(p|b)|cy|Bar(v)?|zigzag|opf|dash|prop|e(e(eq|bar)?|llip|r(t|bar))|Dash|fr|ltri|a(ngrt|r(s(igma|u(psetneq(q)?|bsetneq(q)?))|nothing|t(heta|riangle(left|right))|p(hi|i|ropto)|epsilon|kappa|r(ho)?))|rtri|Arr)|V(scr|cy|opf|dash(l)?|e(e|r(yThinSpace|t(ical(Bar|Separator|Tilde|Line))?|bar))|Dash|vdash|fr|bar))
| (w(scr|circ|opf|p|e(ierp|d(ge(q)?|bar))|fr|r(eath)?)|W(scr|circ|opf|edge|fr))
| (X(scr|i|opf|fr)|x(s(cr|qcup)|h(arr|Arr)|nis|c(irc|up|ap)|i|o(time|dot|p(f|lus))|dtri|u(tri|plus)|vee|fr|wedge|l(arr|Arr)|r(arr|Arr)|map))
| (y(scr|c(y|irc)|icy|opf|u(cy|ml)|en|fr|ac(y|ute))|Y(scr|c(y|irc)|opf|uml|Icy|Ucy|fr|acute|Acy))
| (z(scr|hcy|c(y|aron)|igrarr|opf|dot|e(ta|etrf)|fr|w(nj|j)|acute)|Z(scr|c(y|aron)|Hcy|opf|dot|e(ta|roWidthSpace)|fr|acute))
)
(;)
"""
// Ours, not the bundle's: these two stand in for the tag context that #attribute gets from the grammar tree.
// The bundle's names carry a trailing (?![\w:-]) but no leading guard, so flat they also match a suffix:
// `<div mytitle="x">` colors just the `title`. The behind guard mirrors the bundle's own ahead guard, `:`
// included, so a namespaced `xml:lang` is left alone rather than half-colored.
private const val ATTRIBUTE_NAME_BEHIND = """(?<![\w:-])"""
private const val ATTRIBUTE_VALUE_AHEAD = """(?=\s*=)"""
// #attribute, "HTML5 attributes, not event handlers".
private const val HTML5_ATTRIBUTES =
"""(s(hape|cope|t(ep|art)|ize(s)?|p(ellcheck|an)|elected|lot|andbox|rc(set|doc|lang)?)|h(ttp-equiv|i(dden|gh)|e(ight|aders)|ref(lang)?)|n(o(nce|validate|module)|ame)|c(h(ecked|arset)|ite|o(nt(ent(editable)?|rols)|ords|l(s(pan)?|or))|lass|rossorigin)|t(ype(mustmatch)?|itle|a(rget|bindex)|ranslate)|i(s(map)?|n(tegrity|putmode)|tem(scope|type|id|prop|ref)|d)|op(timum|en)|d(i(sabled|r(name)?)|ownload|e(coding|f(er|ault))|at(etime|a)|raggable)|usemap|p(ing|oster|la(ysinline|ceholder)|attern|reload)|enctype|value|kind|for(m(novalidate|target|enctype|action|method)?)?|w(idth|rap)|l(ist|o(op|w)|a(ng|bel))|a(s(ync)?|c(ce(sskey|pt(-charset)?)|tion)|uto(c(omplete|apitalize)|play|focus)|l(t|low(usermedia|paymentrequest|fullscreen))|bbr)|r(ows(pan)?|e(versed|quired|ferrerpolicy|l|adonly))|m(in(length)?|u(ted|ltiple)|e(thod|dia)|a(nifest|x(length)?)))(?![\w:-])"""
// #attribute, "HTML5 attributes, event handlers".
private const val EVENT_HANDLER_ATTRIBUTES =
"""on(s(croll|t(orage|alled)|u(spend|bmit)|e(curitypolicyviolation|ek(ing|ed)|lect))|hashchange|c(hange|o(ntextmenu|py)|u(t|echange)|l(ick|ose)|an(cel|play(through)?))|t(imeupdate|oggle)|in(put|valid)|o(nline|ffline)|d(urationchange|r(op|ag(start|over|e(n(ter|d)|xit)|leave)?)|blclick)|un(handledrejection|load)|p(opstate|lay(ing)?|a(ste|use|ge(show|hide))|rogress)|e(nded|rror|mptied)|volumechange|key(down|up|press)|focus|w(heel|aiting)|l(oad(start|e(nd|d(data|metadata)))?|anguagechange)|a(uxclick|fterprint|bort)|r(e(s(ize|et)|jectionhandled)|atechange)|m(ouse(o(ut|ver)|down|up|enter|leave|move)|essage(error)?)|b(efore(unload|print)|lur))(?![\w:-])"""
// #tags-valid, meta.tag.structure.$2.{start,end}.html.
private const val STRUCTURE_TAGS =
"""address|article|aside|blockquote|body|button|caption|colgroup|datalist|dd|details|dialog|div|dl|dt|fieldset|figcaption|figure|footer|form|head|header|hgroup|html|h[1-6]|label|legend|li|main|map|menu|meter|nav|ol|optgroup|option|output|p|pre|progress|section|select|slot|summary|table|tbody|td|template|textarea|tfoot|th|thead|tr|ul"""
// #tags-valid, meta.tag.inline.$2.{start,end}.html.
private const val INLINE_TAGS =
"""a|abbr|b|bdi|bdo|cite|code|data|del|dfn|em|i|ins|kbd|mark|q|rp|rt|ruby|s|samp|small|span|strong|sub|sup|time|u|var"""
// #tags-valid, meta.tag.custom.{start,end}.html — the custom-element name production.
private const val CUSTOM_TAG_NAME =
"""[a-zA-Z][.0-9_a-zA-Z\x{00B7}\x{00C0}-\x{00D6}\x{00D8}-\x{00F6}\x{00F8}-\x{037D}\x{037F}-\x{1FFF}\x{200C}-\x{200D}\x{203F}-\x{2040}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}]*-[\-.0-9_a-zA-Z\x{00B7}\x{00C0}-\x{00D6}\x{00D8}-\x{00F6}\x{00F8}-\x{037D}\x{037F}-\x{1FFF}\x{200C}-\x{200D}\x{203F}-\x{2040}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}]*"""
internal val HTML =
LanguageGrammar(
name = "html",
aliases = listOf("htm", "xhtml", "xht", "shtml", "mdoc", "jshtm", "volt", "ejs", "rhtml"),
rules =
listOf(
// #xml-processing — entity.name.tag.html on group 2
TokenRule("(<\\?)(xml)", mapOf(2 to TokenType.KEYWORD)),
// #comment — comment.block.html spans begin through end
TokenRule.comment("<!--[\\s\\S]*?-->"),
// #doctype — entity.name.tag.html on the DOCTYPE word only
TokenRule("<!(?=(?i:DOCTYPE\\s))((?i:DOCTYPE))", mapOf(1 to TokenType.KEYWORD)),
// #cdata — string.other.inline-data.html is the contentName, so only group 1 is colored
TokenRule("<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>", mapOf(1 to TokenType.STRING)),
// #tags-valid, <style> — entity.name.tag.html on group 2, then group 3 of the closing pair
TokenRule("(?i)(<)(style)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(?i)((<)/)(style)\\s*(>)", mapOf(3 to TokenType.KEYWORD)),
// #tags-valid, <script> — entity.name.tag.html on group 2 of both halves
TokenRule("(<)((?i:script))\\b", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(/)((?i:script))(>)", mapOf(2 to TokenType.KEYWORD)),
// #tags-valid, meta.tag.metadata.$2.void.html
TokenRule("(?i)(<)(base|link|meta)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
// #tags-valid, meta.tag.metadata.$2.{start,end}.html
TokenRule("(?i)(<)(noscript|title)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(?i)(</)(noscript|title)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
// #tags-valid, meta.tag.structure.$2.void.html
TokenRule("(?i)(<)(col|hr|input)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
// #tags-valid, meta.tag.structure.$2.{start,end}.html
TokenRule("(?i)(<)($STRUCTURE_TAGS)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(?i)(</)($STRUCTURE_TAGS)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
// #tags-valid, meta.tag.inline.$2.void.html
TokenRule("(?i)(<)(area|br|wbr)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
// #tags-valid, meta.tag.inline.$2.{start,end}.html
TokenRule("(?i)(<)($INLINE_TAGS)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(?i)(</)($INLINE_TAGS)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
// #tags-valid, meta.tag.object.$2.void.html
TokenRule("(?i)(<)(embed|img|param|source|track)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
// #tags-valid, meta.tag.object.$2.{start,end}.html
TokenRule(
"(?i)(<)(audio|canvas|iframe|object|picture|video)(?=\\s|/?>)",
mapOf(2 to TokenType.KEYWORD),
),
TokenRule(
"(?i)(</)(audio|canvas|iframe|object|picture|video)(?=\\s|/?>)",
mapOf(2 to TokenType.KEYWORD),
),
// #tags-valid, obsolete tags. Group 3 is invalid.deprecated.html / invalid.illegal, skipped;
// group 2 is still entity.name.tag.html
TokenRule("(?i)(<)((basefont|isindex))(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(?i)(<)((center|frameset|noembed|noframes))(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(?i)(</)((center|frameset|noembed|noframes))(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(?i)(<)((acronym|big|blink|font|strike|tt|xmp))(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(?i)(</)((acronym|big|blink|font|strike|tt|xmp))(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(?i)(<)((frame))(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(?i)(<)((applet))(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(?i)(</)((applet))(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule(
"(?i)(<)((dir|keygen|listing|menuitem|plaintext|spacer))(?=\\s|/?>)",
mapOf(2 to TokenType.KEYWORD),
),
TokenRule(
"(?i)(</)((dir|keygen|listing|menuitem|plaintext|spacer))(?=\\s|/?>)",
mapOf(2 to TokenType.KEYWORD),
),
// #tags-valid, meta.tag.custom.{start,end}.html
TokenRule("(<)($CUSTOM_TAG_NAME)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
TokenRule("(</)($CUSTOM_TAG_NAME)(?=\\s|/?>)", mapOf(2 to TokenType.KEYWORD)),
// #tags-invalid — the catch-all. Group 3 is invalid.illegal.unrecognized-tag.html, skipped
TokenRule("(</?)((\\w[^\\s>]*))(?<!/)", mapOf(2 to TokenType.KEYWORD)),
// #attribute — entity.other.attribute-name.html on the whole match. The surrounding
// ATTRIBUTE_NAME_BEHIND and ATTRIBUTE_VALUE_AHEAD are ours; see their declarations.
TokenRule.propertyKey(ATTRIBUTE_NAME_BEHIND + HTML5_ATTRIBUTES + ATTRIBUTE_VALUE_AHEAD),
TokenRule.propertyKey(ATTRIBUTE_NAME_BEHIND + "style(?![\\w:-])" + ATTRIBUTE_VALUE_AHEAD),
TokenRule.propertyKey(ATTRIBUTE_NAME_BEHIND + EVENT_HANDLER_ATTRIBUTES + ATTRIBUTE_VALUE_AHEAD),
TokenRule.propertyKey(ATTRIBUTE_NAME_BEHIND + "(data-[a-z\\-]+)(?![\\w:-])" + ATTRIBUTE_VALUE_AHEAD),
// #attribute-interior — string.quoted.{double,single}.html
TokenRule.string("\"[^\"]*\""),
TokenRule.string("'[^']*'"),
// #entities — constant.character.entity.{named,numeric.decimal,numeric.hexadecimal}.html
TokenRule.constant(NAMED_ENTITIES),
TokenRule.constant("(&)#[0-9]+(;)"),
TokenRule.constant("(&)#[xX][0-9a-fA-F]+(;)"),
),
)
@@ -0,0 +1,61 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.code.highlighting.languages
import org.jetbrains.jewel.intui.standalone.code.highlighting.LanguageGrammar
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
internal val JSON =
LanguageGrammar(
name = "json",
aliases =
listOf(
"4dform",
"4dproject",
"avsc",
"bowerrc",
"cssmap",
"geojson",
"gltf",
"har",
"ice",
"ipynb",
"jscsrc",
"jslintrc",
"jsmap",
"json.example",
"json-tmlanguage",
"jsonl",
"jsonld",
"mcmeta",
"sarif",
"slnlaunch",
"tact",
"tfstate",
"tfstate.backup",
"topojson",
"tsmap",
"vuerc",
"webapp",
"webmanifest",
"yy",
"yyp",
),
rules =
listOf(
// Comments must come first
// Strict JSON has no comments, this is a feature of JSONC. No harm in highlighting comments on all
// possible aliases above, though.
TokenRule.comment("/\\*[\\s\\S]*?\\*/"),
TokenRule.comment("//[^\n]*"),
// string.json support.type.property-name.json
// Object keys must come before the generic string rule since an object key is declared just like a
// string value
TokenRule.propertyKey("\"(?:[^\"\\\\]|\\\\.)*\"(?=\\s*:)"),
// String values
TokenRule.string("\"(?:[^\"\\\\]|\\\\.)*\""),
// constant.language.json
TokenRule.constant("\\b(?:true|false|null)\\b"),
// constant.numeric.json, but without the tons of comments because we can't afford it here :P
TokenRule.number("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?"),
),
)
@@ -0,0 +1,29 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.code.highlighting.languages
import org.jetbrains.jewel.intui.standalone.code.highlighting.LanguageGrammar
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
// Patterns adapted from plugins/textmate/lib/bundles/javascript/syntaxes/JavaScriptReact.tmLanguage.json
//
// That bundle is the entire JavaScript grammar re-scoped to `*.js.jsx` plus a handful of tag rules, so this
// grammar layers only the tag rules on top of JAVASCRIPT.rules rather than duplicating them. JSX rules come
// first so they win ties: in `<label for="x">`, `for` is an attribute, not the JS keyword.
private val JSX_RULES =
listOf(
// support.class.component.js.jsx
// capitalized names are components, lowercase ones are DOM elements.
TokenRule("</?([A-Z][\\w\$.]*)(?=[\\s/>])", mapOf(1 to TokenType.TYPE)),
// entity.name.tag.js.jsx
TokenRule("</?([a-z][\\w.:-]*)(?=[\\s/>])", mapOf(1 to TokenType.KEYWORD)),
// entity.other.attribute-name.js.jsx. The lookbehind is ours, standing in for the opening-tag
// context the bundle gets from the tree: an unclosed `<` in expression position (so a comparison
// operator does not qualify), a tag name, then whitespace right before the attribute.
TokenRule.propertyKey("(?<=(?<![\\w\$)\\]])<[a-zA-Z][^<>]*\\s)[A-Za-z_\$][\\w\$:.-]*(?==[\"'{])"),
// constant.character.entity.js.jsx
TokenRule.constant("&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#[xX][0-9a-fA-F]+);"),
)
internal val JSX =
LanguageGrammar(name = "jsx", aliases = listOf("javascriptreact"), rules = JSX_RULES + JAVASCRIPT.rules)
@@ -21,14 +21,17 @@ internal val JAVA =
// keyword.control + storage.modifier.java + storage.type.java
TokenRule.keyword(
"\\b(abstract|assert|break|case|catch|class|const|continue|default|do|else|enum|" +
"extends|final|finally|for|goto|if|implements|import|instanceof|interface|native|new|" +
"extends|final|finally|for|goto|if|implements|import|interface|native|new|" +
"package|private|protected|public|record|return|sealed|static|strictfp|super|switch|" +
"synchronized|this|throw|throws|transient|try|var|volatile|while|permits)\\b"
),
// keyword.operator.instanceof.java
TokenRule.operator("\\b(instanceof)\\b"),
// constant.language.java
TokenRule.constant("\\b(true|false|null)\\b"),
// storage.type.primitive.java
TokenRule.type("\\b(boolean|byte|char|double|float|int|long|short|void)\\b"),
// storage.type.primitive.java — the storage.type family is IntelliJ's keyword key, and its
// own Java highlighter agrees: INT_KEYWORD and friends are in KEYWORD_BIT_SET
TokenRule.keyword("\\b(boolean|byte|char|double|float|int|long|short|void)\\b"),
// entity.name.function.java — identifier immediately before (
TokenRule.functionCall("\\b([A-Za-z_][A-Za-z0-9_]*)\\s*(?=\\()"),
// support.class.java — common boxed types and stdlib roots
@@ -0,0 +1,98 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.code.highlighting.languages
import org.jetbrains.jewel.intui.standalone.code.highlighting.LanguageGrammar
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
// Patterns adapted from plugins/textmate/lib/bundles/javascript/syntaxes/JavaScript.tmLanguage.json
// Aliases from https://github.com/github-linguist/linguist/blob/main/lib/linguist/languages.yml
//
// `$` is an identifier character in JS, so word boundaries are written as (?<![\w$]) / (?![\w$]) rather
// than \b — otherwise `$in`, `$of` and friends (common in query DSLs) get colored as keywords.
internal val JAVASCRIPT =
LanguageGrammar(
name = "javascript",
aliases =
listOf(
"js",
"node",
"_js",
"bones",
"cjs",
"es",
"es6",
"frag",
"gs",
"jake",
"jsb",
"jscad",
"jsfl",
"jslib",
"jsm",
"jspre",
"jss",
"mjs",
"njs",
"pac",
"sjs",
"ssjs",
"xsjs",
"xsjslib",
),
rules =
listOf(
// Comments must come first to avoid matching inside them
// comment.line.shebang.js — \A only matches at the very start of the input
TokenRule.comment("\\A#!.*"),
// comment.block.js + comment.block.documentation.js — both map to COMMENT here
TokenRule.comment("/\\*[\\s\\S]*?\\*/"),
// comment.line.double-slash.js
TokenRule.comment("//[^\n]*"),
// Strings — template literals first. Interpolations are part of the string span.
TokenRule.string("`(?:[^`\\\\]|\\\\.)*`"),
TokenRule.string("\"(?:[^\"\\\\]|\\\\.)*\""),
TokenRule.string("'(?:[^'\\\\]|\\\\.)*'"),
// meta.object-literal.key.js — anchored to `{` or `,` so ternaries (`a ? b : c`) don't match.
// Group 2 is the key; the anchor and the colon stay uncolored.
TokenRule("([{,])\\s*([_\$a-zA-Z][\\w\$]*)\\s*(?=:)", mapOf(2 to TokenType.PROPERTY_KEY)),
// storage.type.function.js + entity.name.function.js
TokenRule.functionDeclaration("(?<![\\w\$])(function)\\s+([_\$a-zA-Z][\\w\$]*)"),
// storage.type.class.js + entity.name.type.js
TokenRule.typeDeclaration("(?<![\\w\$])(class)\\s+([_\$a-zA-Z][\\w\$]*)"),
// storage.type.js + storage.modifier.js. `new` and `import` carry keyword.control in one
// place and keyword.operator.expression in another, which needs the tree to tell apart, so
// they stay keywords.
TokenRule.keyword(
"(?<![\\w\$])(?:var|let|const|function|class|static|get|set|async|await|yield|new|" +
"import|export|from|as|this|super)(?![\\w\$])"
),
// keyword.operator.expression.* — the bundle scopes these as operators, not keywords
TokenRule.operator("(?<![\\w\$])(?:extends|delete|typeof|instanceof|void|in|of)(?![\\w\$])"),
// keyword.control.* — flow
TokenRule.keyword(
"(?<![\\w\$])(?:if|else|for|while|do|break|continue|switch|case|default|return|try|catch|" +
"finally|throw|with|debugger)(?![\\w\$])"
),
// constant.language.js
TokenRule.constant("(?<![\\w\$])(?:true|false|null|undefined|NaN|Infinity)(?![\\w\$])"),
// support.class.* + variable.language.* — well-known globals, before the SCREAMING_CASE rule
// so all-caps names like JSON stay builtins
TokenRule.builtin(
"(?<![\\w\$])(?:globalThis|arguments|console|Math|JSON|Promise|Object|Array|String|Number|" +
"Boolean|Symbol|BigInt|Map|Set|WeakMap|WeakSet|Date|RegExp|Error|Function)(?![\\w\$])"
),
// variable.other.constant.js — SCREAMING_CASE. The trailing lookahead is what stops it from
// matching the leading `M` of `Math`.
TokenRule.constant("(?<![\\w\$])[A-Z][A-Z0-9_\$]*(?![\\w\$])"),
// entity.name.function.js — call sites, after keywords so `if (` stays a keyword
TokenRule.functionCall("([_\$a-zA-Z][\\w\$]*)\\s*(?=\\()"),
// constant.numeric.js — the optional `n` suffix is BigInt
TokenRule.number("(?<![\\w\$])0[bB][01][01_]*n?(?![\\w\$])"),
TokenRule.number("(?<![\\w\$])0[xX][0-9a-fA-F][0-9a-fA-F_]*n?(?![\\w\$])"),
TokenRule.number("(?<![\\w\$])0[oO][0-7][0-7_]*n?(?![\\w\$])"),
TokenRule.number(
"(?<![\\w\$])(?:\\d[\\d_]*(?:\\.[\\d_]*)?|\\.\\d[\\d_]*)(?:[eE][+-]?\\d+)?n?(?![\\w\$])"
),
),
)
@@ -38,8 +38,8 @@ internal val KOTLIN =
TokenRule.functionCall("\\b([A-Za-z_][A-Za-z0-9_]*)\\s*(?=\\()"),
// constant.language.kotlin
TokenRule.constant("\\b(true|false|null)\\b"),
// support.type.kotlin
TokenRule.type(
// support.type.kotlin — IntelliJ's predefined-symbol key, which is our BUILTIN
TokenRule.builtin(
"\\b(String|Int|Long|Double|Float|Boolean|Char|Byte|Short|Unit|Any|Nothing|Array|" +
"List|MutableList|Map|MutableMap|Set|MutableSet|Pair)\\b"
),
@@ -0,0 +1,115 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.code.highlighting.languages
import org.jetbrains.jewel.intui.standalone.code.highlighting.LanguageGrammar
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
// Patterns adapted from plugins/textmate/lib/bundles/python/syntaxes/MagicPython.tmLanguage.json
// That grammar is written in (?x) extended mode with POSIX classes ([[:alpha:]]); both are rewritten here.
// #number-float, constant.numeric.float.python. Kept in the bundle's (?x) mode. The first branch is the
// leading-dot spelling (`.5`, `.5e2`) and every digit run allows `_` separators, the exponent included
// (`1e1_0`); a single hand-rolled decimal rule misses both. Group 1 is the imaginary suffix, scoped
// storage.type.imaginary.number.python, which we fold into the number.
private const val NUMBER_FLOAT =
"""(?x)
(?<! \w)(?:
(?:
\.[0-9](?: _?[0-9] )*
|
[0-9](?: _?[0-9] )* \. [0-9](?: _?[0-9] )*
|
[0-9](?: _?[0-9] )* \.
) (?: [eE][+-]?[0-9](?: _?[0-9] )* )?
|
[0-9](?: _?[0-9] )* (?: [eE][+-]?[0-9](?: _?[0-9] )* )
)([jJ])?\b
"""
// #number-dec, constant.numeric.dec.python. Group 2 is invalid.illegal.dec.python and is dropped.
private const val NUMBER_DEC =
"""(?x)
(?<![\w\.])(?:
[1-9](?: _?[0-9] )*
|
0+
|
[0-9](?: _?[0-9] )* ([jJ])
|
0 ([0-9]+)(?![eE\.])
)\b
"""
internal val PYTHON =
LanguageGrammar(
name = "python",
aliases =
listOf(
"cgi",
"cpy",
"fcgi",
"gyp",
"gypi",
"ipy",
"lmi",
"py3",
"py",
"pyde",
"pyi",
"pyp",
"pyt",
"python3",
"pyw",
"rpy",
"spec",
"tac",
"wsgi",
"xpy",
),
rules =
listOf(
// Comments must come first to avoid matching inside them
TokenRule.comment("#[^\n]*"),
// Strings — triple-quoted first, with optional r/b/u/f prefixes
TokenRule.string("[rRbBuUfF]{0,2}\"\"\"[\\s\\S]*?\"\"\""),
TokenRule.string("[rRbBuUfF]{0,2}'''[\\s\\S]*?'''"),
TokenRule.string("[rRbBuUfF]{0,2}\"(?:[^\"\\\\\n]|\\\\.)*\""),
TokenRule.string("[rRbBuUfF]{0,2}'(?:[^'\\\\\n]|\\\\.)*'"),
// entity.name.function.decorator.python
TokenRule.functionCall("(@[A-Za-z_][A-Za-z0-9_.]*)"),
// def <name> / class <name>
TokenRule.functionDeclaration("\\b(def)\\s+([A-Za-z_][A-Za-z0-9_]*)"),
TokenRule.typeDeclaration("\\b(class)\\s+([A-Za-z_][A-Za-z0-9_]*)"),
// keyword.control.flow.python + storage.modifier.declaration.python + operators that are words.
// `match` and `case` are soft keywords and far too common as identifiers, so they're left out.
TokenRule.keyword(
"\\b(?:def|class|lambda|return|yield|import|from|as|pass|break|continue|if|elif|else|for|" +
"while|try|except|finally|raise|with|assert|del|global|nonlocal|async|await)\\b"
),
// keyword.operator.logical.python, group 1 of #operator
TokenRule.operator("\\b(?:and|or|not|in|is)\\b"),
// constant.language.python
TokenRule.constant("\\b(?:True|False|None|NotImplemented|Ellipsis|__debug__)\\b"),
// support.type.python — IntelliJ's predefined-symbol key, which is our BUILTIN
TokenRule.builtin(
"\\b(?:bool|bytearray|bytes|complex|dict|float|frozenset|int|list|object|property|set|" +
"slice|str|tuple|type|super|classmethod|staticmethod)\\b"
),
// support.function.builtin.python + variable.language.special.self
TokenRule.builtin(
"\\b(?:self|cls|print|len|range|open|input|abs|all|any|enumerate|filter|format|getattr|" +
"hasattr|hash|id|isinstance|issubclass|iter|map|max|min|next|repr|reversed|round|" +
"setattr|sorted|sum|zip|vars|dir|eval|exec|divmod|chr|ord|hex|oct|bin|callable)\\b"
),
// entity.name.function.call — after keywords so `if (` stays a keyword
TokenRule.functionCall("\\b([A-Za-z_][A-Za-z0-9_]*)\\s*(?=\\()"),
// #number, in the bundle's own order so a float beats the decimal rule at the same offset.
// The prefixes are storage.type.number.python in the bundle; we fold them into the number.
TokenRule.number(NUMBER_FLOAT),
TokenRule.number(NUMBER_DEC),
TokenRule.number("""(?x) (?<![\w\.]) (0[xX]) (_?[0-9a-fA-F])+ \b"""),
TokenRule.number("""(?x) (?<![\w\.]) (0[oO]) (_?[0-7])+ \b"""),
TokenRule.number("""(?x) (?<![\w\.]) (0[bB]) (_?[01])+ \b"""),
// #number-long, python 2 long ints
TokenRule.number("""(?x) (?<![\w\.]) ([1-9][0-9]* | 0) ([lL]) \b"""),
),
)
@@ -0,0 +1,390 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.code.highlighting.languages
import org.jetbrains.jewel.intui.standalone.code.highlighting.LanguageGrammar
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
// Patterns ported from plugins/textmate/lib/bundles/sql/syntaxes/sql.tmLanguage.json.
//
// Differences you can see:
// - Nested block comments end early: `/* a /* b */ c */` stops coloring at the first `*/`, where TextMate
// nests them. java.util.regex cannot recurse.
// - storage.modifier (`primary key`, `references`, `default`) has no TokenType of its own, so it is
// colored as a keyword.
//
// Three things that look like bugs but are the bundle's. Its only standalone number rule is `\b\d+\b`, so
// `3.14` is two numbers and `1e10` is not a number at all. It has no constant.language rule, so `null` is a
// keyword and `true`/`false` are not colored. And the `@name` and `[name]` rules deliberately map no
// captures: they still consume their match, which is what keeps `[select]` off the keyword rule.
// storage.type.sql on the type names, constant.numeric.sql on the length and precision arguments. Kept in the
// bundle's own (?xi) extended mode, comments included.
private const val STORAGE_TYPES =
"""(?xi)
# normal stuff, capture 1
\b(bigint|bigserial|bit|boolean|box|bytea|cidr|circle|date|double\sprecision|inet|int|integer|
line|lseg|macaddr|money|oid|path|point|polygon|real|serial|smallint|sysdate|text)\b
# numeric suffix, capture 2 + 3i
|\b(bit\svarying|character\s(?:varying)?|tinyint|var\schar|float|interval)\((\d+)\)
# optional numeric suffix, capture 4 + 5i
|\b(char|number|varchar\d?)\b(?:\((\d+)\))?
# special case, capture 6 + 7i + 8i
|\b(numeric|decimal)\b(?:\((\d+),(\d+)\))?
# special case, captures 9, 10i, 11
|\b(times?)\b(?:\((\d+)\))?(\swith(?:out)?\stime\szone\b)?
# special case, captures 12, 13, 14i, 15
|\b(timestamp)(?:(s|tz))?\b(?:\((\d+)\))?(\s(with|without)\stime\szone\b)?
"""
// keyword.other.sql, the bundle's catch-all keyword list. `create(\\s+or\\s+alter)?` is double-escaped in the
// JSON source, so the optional group is a literal backslash followed by `s+or...` and can never match real SQL;
// it is copied as-is rather than repaired.
private const val OTHER_KEYWORDS =
"\\b(?i)(abort|abort_after_wait|absent|absolute|accent_sensitivity|acceptable_cursopt|acp|action|" +
"activation|add|address|admin|aes_128|aes_192|aes_256|affinity|after|aggregate|algorithm|" +
"all_constraints|all_errormsgs|all_indexes|all_levels|all_results|allow_connections|allow_dup_row|" +
"allow_encrypted_value_modifications|allow_page_locks|allow_row_locks|allow_snapshot_isolation|alter|" +
"altercolumn|always|anonymous|ansi_defaults|ansi_null_default|ansi_null_dflt_off|ansi_null_dflt_on|" +
"ansi_nulls|ansi_padding|ansi_warnings|appdomain|append|application|apply|arithabort|arithignore|" +
"array|assembly|asymmetric|asynchronous_commit|at|atan2|atomic|attach|attach_force_rebuild_log|" +
"attach_rebuild_log|audit|auth_realm|authentication|auto|auto_cleanup|auto_close|" +
"auto_create_statistics|auto_drop|auto_shrink|auto_update_statistics|auto_update_statistics_async|" +
"automated_backup_preference|automatic|autopilot|availability|availability_mode|backup|" +
"backup_priority|base64|basic|batches|batchsize|before|between|bigint|binary|binding|bit|block|" +
"blockers|blocksize|bmk|both|break|broker|broker_instance|bucket_count|buffer|buffercount|" +
"bulk_logged|by|call|caller|card|case|catalog|catch|cert|certificate|change_retention|" +
"change_tracking|change_tracking_context|changes|char|character|character_set|check_expiration|" +
"check_policy|checkconstraints|checkindex|checkpoint|checksum|cleanup_policy|clear|clear_port|close|" +
"clustered|codepage|collection|column_encryption_key|column_master_key|columnstore|" +
"columnstore_archive|colv_80_to_100|colv_100_to_80|commit_differential_base|committed|" +
"compatibility_level|compress_all_row_groups|compression|compression_delay|concat_null_yields_null|" +
"concatenate|configuration|connect|connection|containment|continue|continue_after_error|contract|" +
"contract_name|control|conversation|conversation_group_id|conversation_handle|copy|copy_only|" +
"count_rows|counter|create(\\\\s+or\\\\s+alter)?|credential|cross|cryptographic|" +
"cryptographic_provider|cube|cursor|cursor_close_on_commit|cursor_default|data|data_compression|" +
"data_flush_interval_seconds|data_mirroring|data_purity|data_source|database|database_name|" +
"database_snapshot|datafiletype|date_correlation_optimization|date|datefirst|dateformat|date_format|" +
"datetime|datetime2|datetimeoffset|day(s)?|db_chaining|dbid|dbidexec|dbo_only|deadlock_priority|" +
"deallocate|dec|decimal|declare|decrypt|decrypt_a|decryption|default_database|" +
"default_fulltext_language|default_language|default_logon_domain|default_schema|definition|delay|" +
"delayed_durability|delimitedtext|density_vector|dependent|des|description|desired_state|desx|" +
"differential|digest|disable|disable_broker|disable_def_cnst_chk|disabled|disk|distinct|distributed|" +
"distribution|drop|drop_existing|dts_buffers|dump|durability|dynamic|edition|elements|else|emergency|" +
"empty|enable|enable_broker|enabled|encoding|encrypted|encrypted_value|encryption|encryption_type|" +
"end|endpoint|endpoint_url|enhancedintegrity|entry|error_broker_conversations|errorfile|estimateonly|" +
"event|except|exec|executable|execute|exists|expand|expiredate|expiry_date|explicit|external|" +
"external_access|failover|failover_mode|failure_condition_level|fast|fast_forward|fastfirstrow|" +
"federated_service_account|fetch|field_terminator|fieldterminator|file|filelistonly|filegroup|" +
"filegrowth|filename|filestream|filestream_log|filestream_on|filetable|file_format|filter|first_row|" +
"fips_flagger|fire_triggers|first|firstrow|float|flush_interval_seconds|fmtonly|following|for|force|" +
"force_failover_allow_data_loss|force_service_allow_data_loss|forced|forceplan|formatfile|" +
"format_options|format_type|formsof|forward_only|free_cursors|free_exec_context|fullscan|fulltext|" +
"fulltextall|fulltextkey|function|generated|get|geography|geometry|global|go|goto|governor|guid|" +
"hadoop|hardening|hash|hashed|header_limit|headeronly|health_check_timeout|hidden|hierarchyid|" +
"histogram|histogram_steps|hits_cursors|hits_exec_context|hour(s)?|http|identity|identity_value|if|" +
"ifnull|ignore|ignore_constraints|ignore_dup_key|ignore_dup_row|ignore_triggers|image|immediate|" +
"implicit_transactions|include|include_null_values|incremental|index|inflectional|init|initiator|" +
"insensitive|insert|instead|int|integer|integrated|intersect|intermediate|interval_length_minutes|" +
"into|inuse_cursors|inuse_exec_context|io|is|isabout|iso_week|isolation|job_tracker_location|json|" +
"keep|keep_nulls|keep_replication|keepdefaults|keepfixed|keepidentity|keepnulls|kerberos|key|" +
"key_path|key_source|key_store_provider_name|keyset|kill|kilobytes_per_batch|labelonly|langid|" +
"language|last|lastrow|leading|legacy_cardinality_estimation|length|level|lifetime|lineage_80_to_100|" +
"lineage_100_to_80|listener_ip|listener_port|load|loadhistory|lob_compaction|local|" +
"local_service_name|locate|location|lock_escalation|lock_timeout|lockres|log|login|login_type|loop|" +
"manual|mark_in_use_for_removal|masked|master|match|matched|max_queue_readers|max_duration|" +
"max_outstanding_io_per_volume|maxdop|maxerrors|maxlength|maxtransfersize|max_plans_per_query|" +
"max_storage_size_mb|mediadescription|medianame|mediapassword|memogroup|memory_optimized|merge|" +
"message|message_forward_size|message_forwarding|microsecond|millisecond|minute(s)?|mirror_address|" +
"misses_cursors|misses_exec_context|mixed|modify|money|month|move|multi_user|must_change|name|" +
"namespace|nanosecond|native|native_compilation|nchar|ncharacter|nested_triggers|never|new_account|" +
"new_broker|newname|next|no|no_browsetable|no_checksum|no_compression|no_infomsgs|no_triggers|" +
"no_truncate|nocount|noexec|noexpand|noformat|noinit|nolock|nonatomic|nonclustered|nondurable|none|" +
"norecompute|norecovery|noreset|norewind|noskip|not|notification|nounload|now|nowait|ntext|ntlm|" +
"nulls|numeric|numeric_roundabort|nvarchar|object|objid|oem|offline|old_account|online|" +
"operation_mode|open|openjson|optimistic|option|orc|out|outer|output|over|override|owner|ownership|" +
"pad_index|page|page_checksum|page_verify|pagecount|paglock|param|parameter_sniffing|" +
"parameter_type_expansion|parameterization|parquet|parseonly|partial|partition|partner|password|path|" +
"pause|percentage|permission_set|persisted|period|physical_only|plan_forcing_mode|policy|pool|" +
"population|ports|preceding|precision|predicate|presume_abort|primary|primary_role|print|prior|" +
"priority |priority_level|private|proc(edure)?|procedure_name|profile|provider|quarter|" +
"query_capture_mode|query_governor_cost_limit|query_optimizer_hotfixes|query_store|queue|" +
"quoted_identifier|raiserror|range|raw|rcfile|rc2|rc4|rc4_128|rdbms|read_committed_snapshot|read|" +
"read_only|read_write|readcommitted|readcommittedlock|readonly|readpast|readuncommitted|readwrite|" +
"real|rebuild|receive|recmodel_70backcomp|recompile|reconfigure|recovery|recursive|" +
"recursive_triggers|redo_queue|reject_sample_value|reject_type|reject_value|relative|remote|" +
"remote_data_archive|remote_proc_transactions|remote_service_name|remove|removed_cursors|" +
"removed_exec_context|reorganize|repeat|repeatable|repeatableread|replace|replica|replicated|" +
"replnick_100_to_80|replnickarray_80_to_100|replnickarray_100_to_80|required|required_cursopt|" +
"resample|reset|resource|resource_manager_location|respect|restart|restore|restricted_user|resume|" +
"retaindays|retention|return|revert|rewind|rewindonly|returns|robust|role|rollup|root|round_robin|" +
"route|row|rowdump|rowguidcol|rowlock|row_terminator|rows|rows_per_batch|rowsets_only|rowterminator|" +
"rowversion|rsa_1024|rsa_2048|rsa_3072|rsa_4096|rsa_512|safe|safety|sample|save|scalar|schema|" +
"schemabinding|scoped|scroll|scroll_locks|sddl|second|secexpr|seconds|secondary|secondary_only|" +
"secondary_role|secret|security|securityaudit|selective|self|send|sent|sequence|serde_method|" +
"serializable|server|service|service_broker|service_name|service_objective|session_timeout|session|" +
"sessions|seterror|setopts|sets|shard_map_manager|shard_map_name|sharded|shared_memory|shortest_path|" +
"show_statistics|showplan_all|showplan_text|showplan_xml|showplan_xml_with_recompile|shrinkdb|" +
"shutdown|sid|signature|simple|single_blob|single_clob|single_nclob|single_user|singleton|site|size|" +
"size_based_cleanup_mode|skip|smalldatetime|smallint|smallmoney|snapshot|snapshot_import|" +
"snapshotrestorephase|soap|softnuma|sort_in_tempdb|sorted_data|sorted_data_reorg|spatial|sql|" +
"sql_bigint|sql_binary|sql_bit|sql_char|sql_date|sql_decimal|sql_double|sql_float|sql_guid|" +
"sql_handle|sql_longvarbinary|sql_longvarchar|sql_numeric|sql_real|sql_smallint|sql_time|" +
"sql_timestamp|sql_tinyint|sql_tsi_day|sql_tsi_frac_second|sql_tsi_hour|sql_tsi_minute|sql_tsi_month|" +
"sql_tsi_quarter|sql_tsi_second|sql_tsi_week|sql_tsi_year|sql_type_date|sql_type_time|" +
"sql_type_timestamp|sql_varbinary|sql_varchar|sql_variant|sql_wchar|sql_wlongvarchar|ssl|ssl_port|" +
"standard|standby|start|start_date|started|stat_header|state|statement|static|statistics|" +
"statistics_incremental|statistics_norecompute|statistics_only|statman|stats|stats_stream|status|" +
"stop|stop_on_error|stopat|stopatmark|stopbeforemark|stoplist|stopped|string_delimiter|subject|" +
"supplemental_logging|supported|suspend|symmetric|synchronous_commit|synonym|sysname|system|" +
"system_time|system_versioning|table|tableresults|tablock|tablockx|take|tape|target|target_index|" +
"target_partition|target_recovery_time|tcp|temporal_history_retention|text|textimage_on|then|" +
"thesaurus|throw|time|timeout|timestamp|tinyint|to|top|torn_page_detection|track_columns_updated|" +
"trailing|tran|transaction|transfer|transform_noise_words|triple_des|triple_des_3key|truncate|" +
"trustworthy|try|tsql|two_digit_year_cutoff|type|type_desc|type_warning|tzoffset|uid|unbounded|" +
"uncommitted|unique|uniqueidentifier|unlimited|unload|unlock|unsafe|updlock|url|use|useplan|" +
"useroptions|use_type_default|using|utcdatetime|valid_xml|validation|value|values|varbinary|varchar|" +
"vector|verbose|verifyonly|version|view_metadata|virtual_device|visiblity|wait_at_low_priority|" +
"waitfor|webmethod|week|weekday|weight|well_formed_xml|when|while|widechar|widechar_ansi|widenative|" +
"window|windows|with|within|within group|witness|without|without_array_wrapper|workload|wsdl|" +
"xact_abort|xlock|xml|xmlschema|xquery|xsinil|year|zone)\\b"
internal val SQL =
LanguageGrammar(
name = "sql",
aliases = listOf("cql", "db2", "ddl", "dml", "dsql", "inc", "mysql", "prc", "sql", "tab", "udf", "viw"),
rules =
listOf(
// text.variable and text.bracketed: matched and consumed, but not colored — see the header.
TokenRule("((?<!@)@)\\b(\\w+)\\b", emptyMap()),
TokenRule("(\\[)[^\\]]*(\\])", emptyMap()),
// #comments — comment.line.double-dash.sql, then #comment-block's comment.block
TokenRule.comment("--[^\\r\\n]*+"),
TokenRule.comment("/\\*[\\s\\S]*?\\*/"),
// meta.create.sql: keyword.other.create.sql, keyword.other.sql, entity.name.function.sql
TokenRule(
"(?m)(?i:^\\s*(create(?:\\s+or\\s+replace)?)\\s+(aggregate|conversion|database|domain|" +
"function|group|(unique\\s+)?index|language|operator class|operator|rule|schema|" +
"sequence|table|tablespace|trigger|type|user|view)\\s+)(['\"`]?)(\\w+)\\4",
mapOf(1 to TokenType.KEYWORD, 2 to TokenType.KEYWORD, 5 to TokenType.FUNCTION_CALL),
),
// meta.drop.sql: keyword.other.create.sql, keyword.other.sql
TokenRule(
"(?m)(?i:^\\s*(drop)\\s+(aggregate|conversion|database|domain|function|group|index|" +
"language|operator class|operator|rule|schema|sequence|table|tablespace|trigger|type|" +
"user|view))",
mapOf(1 to TokenType.KEYWORD, 2 to TokenType.KEYWORD),
),
// meta.drop.sql: keyword.other.create.sql, keyword.other.table.sql, entity.name.function.sql,
// keyword.other.cascade.sql
TokenRule(
"(?i:\\s*(drop)\\s+(table)\\s+(\\w+)(\\s+cascade)?\\b)",
mapOf(
1 to TokenType.KEYWORD,
2 to TokenType.KEYWORD,
3 to TokenType.FUNCTION_CALL,
4 to TokenType.KEYWORD,
),
),
// meta.alter.sql: keyword.other.create.sql, keyword.other.table.sql
TokenRule(
"(?m)(?i:^\\s*(alter)\\s+(aggregate|conversion|database|domain|function|group|index|" +
"language|operator class|operator|proc(edure)?|rule|schema|sequence|table|tablespace|" +
"trigger|type|user|view)\\s+)",
mapOf(1 to TokenType.KEYWORD, 2 to TokenType.KEYWORD),
),
// storage.type.sql + constant.numeric.sql. storage.type is IntelliJ's keyword key.
TokenRule(
STORAGE_TYPES,
mapOf(
1 to TokenType.KEYWORD,
2 to TokenType.KEYWORD,
3 to TokenType.NUMBER,
4 to TokenType.KEYWORD,
5 to TokenType.NUMBER,
6 to TokenType.KEYWORD,
7 to TokenType.NUMBER,
8 to TokenType.NUMBER,
9 to TokenType.KEYWORD,
10 to TokenType.NUMBER,
11 to TokenType.KEYWORD,
12 to TokenType.KEYWORD,
13 to TokenType.KEYWORD,
14 to TokenType.NUMBER,
15 to TokenType.KEYWORD,
),
),
// storage.modifier.sql — KEYWORD is our choice, there is no TokenType for storage.modifier
TokenRule.keyword(
"(?i:\\b((?:primary|foreign)\\s+key|references|on\\s+(delete|update)(\\s+cascade)?|" +
"nocheck|check|constraint|collate|default)\\b)"
),
// constant.numeric.sql — integers only; the bundle has no float or exponent rule
TokenRule.number("\\b\\d+\\b"),
// keyword.other.DML.sql
TokenRule.keyword(
"(?i:\\b(select(\\s+(all|distinct))?|insert\\s+(ignore\\s+)?into|update|delete|from|set|" +
"where|group\\s+by|or|like|and|union(\\s+all)?|having|order\\s+by|limit|cross\\s+join|" +
"join|straight_join|(inner|(left|right|full)(\\s+outer)?)\\s+join|" +
"natural(\\s+(inner|(left|right|full)(\\s+outer)?))?\\s+join)\\b)"
),
// keyword.other.DDL.create.II.sql — this is where `null` is handled
TokenRule.keyword("(?i:\\b(on|off|((is\\s+)?not\\s+)?null)\\b)"),
// keyword.other.DML.II.sql
TokenRule.keyword("(?i:\\bvalues\\b)"),
// keyword.other.LUW.sql
TokenRule.keyword(
"(?i:\\b(begin(\\s+work)?|start\\s+transaction|commit(\\s+work)?|rollback(\\s+work)?)\\b)"
),
// keyword.other.authorization.sql
TokenRule.keyword("(?i:\\b(grant(\\swith\\sgrant\\soption)?|revoke)\\b)"),
// keyword.other.data-integrity.sql
TokenRule.keyword("(?i:\\bin\\b)"),
// keyword.other.object-comments.sql
TokenRule.keyword(
"(?m)(?i:^\\s*(comment\\s+on\\s+(table|column|aggregate|constraint|database|domain|" +
"function|index|operator|rule|schema|sequence|trigger|type|view))\\s+)"
),
// keyword.other.alias.sql
TokenRule.keyword("(?i)\\bAS\\b"),
// keyword.other.order.sql
TokenRule.keyword("(?i)\\b(DESC|ASC)\\b"),
// keyword.operator.star.sql, .comparison.sql, .math.sql, .concatenator.sql
TokenRule.operator("\\*"),
TokenRule.operator("[!<>]?=|<>|<|>"),
TokenRule.operator("-|\\+|/"),
TokenRule.operator("\\|\\|"),
// support.function.aggregate.sql
TokenRule.functionCall(
"(?i)\\b(approx_count_distinct|approx_percentile_cont|approx_percentile_disc|avg|" +
"checksum_agg|count|count_big|group|grouping|grouping_id|max|min|sum|stdev|stdevp|var|" +
"varp)\\b\\s*\\("
),
// support.function.analytic.sql
TokenRule.functionCall(
"(?i)\\b(cume_dist|first_value|lag|last_value|lead|percent_rank|percentile_cont|" +
"percentile_disc)\\b\\s*\\("
),
// support.function.bitmanipulation.sql
TokenRule.functionCall("(?i)\\b(bit_count|get_bit|left_shift|right_shift|set_bit)\\b\\s*\\("),
// support.function.conversion.sql
TokenRule.functionCall("(?i)\\b(cast|convert|parse|try_cast|try_convert|try_parse)\\b\\s*\\("),
// support.function.collation.sql
TokenRule.functionCall("(?i)\\b(collationproperty|tertiary_weights)\\b\\s*\\("),
// support.function.cryptographic.sql
TokenRule.functionCall(
"(?i)\\b(asymkey_id|asymkeyproperty|certproperty|cert_id|crypt_gen_random|" +
"decryptbyasymkey|decryptbycert|decryptbykey|decryptbykeyautoasymkey|" +
"decryptbykeyautocert|decryptbypassphrase|encryptbyasymkey|encryptbycert|encryptbykey|" +
"encryptbypassphrase|hashbytes|is_objectsigned|key_guid|key_id|key_name|" +
"signbyasymkey|signbycert|symkeyproperty|verifysignedbycert|" +
"verifysignedbyasymkey)\\b\\s*\\("
),
// support.function.cursor.sql
TokenRule.functionCall("(?i)\\b(cursor_status)\\b\\s*\\("),
// support.function.datetime.sql
TokenRule.functionCall(
"(?i)\\b(sysdatetime|sysdatetimeoffset|sysutcdatetime|current_time(stamp)?|getdate|" +
"getutcdate|datename|datepart|day|month|year|datefromparts|datetime2fromparts|" +
"datetimefromparts|datetimeoffsetfromparts|smalldatetimefromparts|timefromparts|" +
"datediff|dateadd|datetrunc|eomonth|switchoffset|todatetimeoffset|isdate|" +
"date_bucket)\\b\\s*\\("
),
// support.function.datatype.sql
TokenRule.functionCall(
"(?i)\\b(datalength|ident_current|ident_incr|ident_seed|identity|" +
"sql_variant_property)\\b\\s*\\("
),
// support.function.expression.sql
TokenRule.functionCall("(?i)\\b(coalesce|nullif)\\b\\s*\\("),
// support.function.globalvar.sql
TokenRule.functionCall(
"(?<!@)@@(?i)\\b(cursor_rows|connections|cpu_busy|datefirst|dbts|error|fetch_status|" +
"identity|idle|io_busy|langid|language|lock_timeout|max_connections|max_precision|" +
"nestlevel|options|packet_errors|pack_received|pack_sent|procid|remserver|rowcount|" +
"servername|servicename|spid|textsize|timeticks|total_errors|total_read|total_write|" +
"trancount|version)\\b\\s*\\("
),
// support.function.json.sql
TokenRule.functionCall(
"(?i)\\b(json|isjson|json_object|json_array|json_value|json_query|json_modify|" +
"json_path_exists)\\b\\s*\\("
),
// support.function.logical.sql
TokenRule.functionCall("(?i)\\b(choose|iif|greatest|least)\\b\\s*\\("),
// support.function.mathematical.sql
TokenRule.functionCall(
"(?i)\\b(abs|acos|asin|atan|atn2|ceiling|cos|cot|degrees|exp|floor|log|log10|pi|power|" +
"radians|rand|round|sign|sin|sqrt|square|tan)\\b\\s*\\("
),
// support.function.metadata.sql
TokenRule.functionCall(
"(?i)\\b(app_name|applock_mode|applock_test|assemblyproperty|col_length|col_name|" +
"columnproperty|database_principal_id|databasepropertyex|db_id|db_name|file_id|" +
"file_idex|file_name|filegroup_id|filegroup_name|filegroupproperty|fileproperty|" +
"fulltextcatalogproperty|fulltextserviceproperty|index_col|indexkey_property|" +
"indexproperty|object_definition|object_id|object_name|object_schema_name|" +
"objectproperty|objectpropertyex|original_db_name|parsename|schema_id|schema_name|" +
"scope_identity|serverproperty|stats_date|type_id|type_name|typeproperty)\\b\\s*\\("
),
// support.function.ranking.sql
TokenRule.functionCall("(?i)\\b(rank|dense_rank|ntile|row_number)\\b\\s*\\("),
// support.function.rowset.sql
TokenRule.functionCall(
"(?i)\\b(generate_series|opendatasource|openjson|openrowset|openquery|openxml|predict|" +
"string_split)\\b\\s*\\("
),
// support.function.security.sql
TokenRule.functionCall(
"(?i)\\b(certencoded|certprivatekey|current_user|database_principal_id|" +
"has_perms_by_name|is_member|is_rolemember|is_srvrolemember|original_login|" +
"permissions|pwdcompare|pwdencrypt|schema_id|schema_name|session_user|suser_id|" +
"suser_sid|suser_sname|system_user|suser_name|user_id|user_name)\\b\\s*\\("
),
// support.function.string.sql
TokenRule.functionCall(
"(?i)\\b(ascii|char|charindex|concat|difference|format|left|len|lower|ltrim|nchar|nodes|" +
"patindex|quotename|replace|replicate|reverse|right|rtrim|soundex|space|str|" +
"string_agg|string_escape|string_split|stuff|substring|translate|trim|unicode|" +
"upper)\\b\\s*\\("
),
// support.function.system.sql
TokenRule.functionCall(
"(?i)\\b(binary_checksum|checksum|compress|connectionproperty|context_info|" +
"current_request_id|current_transaction_id|decompress|error_line|error_message|" +
"error_number|error_procedure|error_severity|error_state|formatmessage|" +
"get_filestream_transaction_context|getansinull|host_id|host_name|isnull|isnumeric|" +
"min_active_rowversion|newid|newsequentialid|rowcount_big|session_context|session_id|" +
"xact_state)\\b\\s*\\("
),
// support.function.textimage.sql
TokenRule.functionCall("(?i)\\b(patindex|textptr|textvalid)\\b\\s*\\("),
// support.function.vector.sql
TokenRule.functionCall("(?i)\\b(vector_distance|vector_norm|vector_normalize)\\b\\s*\\("),
// constant.other.database-name.sql and constant.other.table-name.sql
TokenRule("(\\w+?)\\.(\\w+)", mapOf(1 to TokenType.CONSTANT, 2 to TokenType.CONSTANT)),
// #strings — string.quoted.single.sql, the fast path first and the begin/end fallback second,
// exactly as the bundle orders them; then the same pair for backticks and double quotes, then
// string.other.quoted.brackets.sql
TokenRule.string("(?:(?<![a-zA-Z0-9_])(N))?(')[^']*(')"),
TokenRule.string("'(?:\\\\.|[^'])*+'"),
TokenRule.string("(`)[^`\\\\]*(`)"),
TokenRule.string("`(?:\\\\.|[^`])*+`"),
TokenRule.string("(\")[^\"#]*(\")"),
TokenRule.string("\"[^\"]*+\""),
TokenRule.string("%\\{[^}]*+\\}"),
// #regexps — string.regexp.sql and string.regexp.modr.sql. These fire far less often than they
// look like they would: keyword.operator.math.sql above matches `/` at the same offset and is
// listed first, so it wins the tie, in this engine as in TextMate.
TokenRule.string("/(?=\\S.*/)(?:\\\\/|[^/])*+/"),
TokenRule.string("%r\\{[^}]*+\\}"),
// keyword.other.sql
TokenRule.keyword(OTHER_KEYWORDS),
),
)
@@ -0,0 +1,196 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.code.highlighting.languages
import org.jetbrains.jewel.intui.standalone.code.highlighting.LanguageGrammar
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
// Patterns ported from plugins/textmate/lib/bundles/shellscript/syntaxes/shell-unix-bash.tmLanguage.json.
//
// Differences you can see, all of them because TextMate tracks which construct it is inside and we match
// one regex at a time:
// - Case patterns read as commands: `start)` gets the command color where TextMate leaves it plain. Every
// guard that excludes a word before `)` also excludes `$(pwd)`, which is far more common.
// - `IFS= read -r line` leaves `read` plain, because an assignment prefix does not open command position.
// - `declare -A m` leaves `m` plain. Only `m=` is recognized as an assignment target.
// - Globs (`*`, `?`) and the `${...}` expansion operators stay plain. Matched flat they would color every
// `*`, `?`, `:`, `#`, `/`, `%` and `@` in the file.
// - Test and arithmetic operators stay plain, so `-f` in `[ -f x ]` reads as a command option instead.
// - Backticks and `<(cmd)` are one flat string; TextMate highlights the commands inside them.
// - An unterminated heredoc colors nothing, where TextMate runs the string to end of file.
// - `alias` reads as a builtin rather than a keyword, and so does `$VAR`: IntelliJ has no separate color
// for shell variables.
// Command position, which the bundle gets from a \G anchor three levels down the tree. The `((` case is
// excluded so an arithmetic operand does not read as a command.
private const val COMMAND_START =
"(?m)(?<=^|[;|&!{`]|(?:^|[^(])\\(|(?:^|[\\t ])(?:until|while|elif|else|then|do|if) )[\\t ]*+"
// What a command cannot start with. `}`, `]` and `$(` are ours: the bundle eats them as punctuation before
// a command can start, so flat a lone `}` closing a function body would read as a command name.
private const val NOT_COMMAND_CHAR = "(?![!&|(){}\\[\\]<>#;\\t\\n ]|\\$\\(|$)"
// #command_statement's begin also refuses to start a command on a word that is really a keyword.
private const val NOT_KEYWORD =
"(?!(?:nocorrect|readonly|function|foreach|coproc|logout|export|select|repeat|pushd|until|while|local|" +
"case|done|elif|else|esac|popd|then|time|for|end|fi|do|in|if)(?:[\\t ]|$))"
// #numeric_literal.
private const val NUMBER =
"(?:(?:(?:(?:(?:(0[xX][0-9A-Fa-f]+)|(0\\d+))|(\\d{1,2}#[0-9a-zA-Z@_]+))|(-?\\d+(?:\\.\\d+)))|" +
"(-?\\d+(?:\\.\\d+)+))|(-?\\d+))"
private val NUMBER_GROUPS =
mapOf(
1 to TokenType.NUMBER,
2 to TokenType.NUMBER,
3 to TokenType.NUMBER,
4 to TokenType.NUMBER,
5 to TokenType.NUMBER,
6 to TokenType.NUMBER,
)
internal val SHELL =
LanguageGrammar(
name = "shellscript",
aliases =
listOf(
"bash",
"bashrc",
"bats",
"csh",
"ebuild",
"eclass",
"envrc",
"fish",
"ksh",
"openrc",
"profile",
"sh",
"shell",
"shell-script",
"shellcheck",
"tcsh",
"zsh",
"zsh-theme",
"zshrc",
),
rules =
listOf(
// comment.line.number-sign.shell. The # must open the line or follow whitespace, and the
// bundle's shebang alternative collapses into this one: same scope.
TokenRule.comment("(?m)(?<=^|[\\t ])#[^\\r\\n]*"),
// #heredoc, all four spellings. Group 1 is the operator, the last group the body; the
// delimiter is punctuation, so it stays bare.
TokenRule(
"(?m)((?<!<)<<-)[\\t ]*+([\"'])[\\t ]*+([^\"'\\r\\n]+?)(?=\\s|;|&|<|\"|')\\2" +
"[^\\r\\n]*+(\\n[\\s\\S]*?)^\\t*+\\3(?=\\s|;|&|$)",
mapOf(1 to TokenType.OPERATOR, 4 to TokenType.STRING),
),
TokenRule(
"(?m)((?<!<)<<(?!<))[\\t ]*+([\"'])[\\t ]*+([^\"'\\r\\n]+?)(?=\\s|;|&|<|\"|')\\2" +
"[^\\r\\n]*+(\\n[\\s\\S]*?)^\\3(?=\\s|;|&|$)",
mapOf(1 to TokenType.OPERATOR, 4 to TokenType.STRING),
),
TokenRule(
"(?m)((?<!<)<<-)[\\t ]*+([^\"' \\t\\r\\n]+)(?=\\s|;|&|<|\"|')" +
"[^\\r\\n]*+(\\n[\\s\\S]*?)^\\t*+\\2(?=\\s|;|&|$)",
mapOf(1 to TokenType.OPERATOR, 3 to TokenType.STRING),
),
TokenRule(
"(?m)((?<!<)<<(?!<))[\\t ]*+([^\"' \\t\\r\\n]+)(?=\\s|;|&|<|\"|')" +
"[^\\r\\n]*+(\\n[\\s\\S]*?)^\\2(?=\\s|;|&|$)",
mapOf(1 to TokenType.OPERATOR, 3 to TokenType.STRING),
),
// #string — the bundle's begin/end pairs, fused. Single quotes take no escapes in shell,
// which is why only the double-quoted forms carry one.
TokenRule.string("\\$'(?:\\\\.|[^'\\\\])*'"),
TokenRule.string("'[^']*'"),
TokenRule.string("\\$?\"(?:\\\\.|[^\"\\\\])*\""),
// string.interpolated.backtick.shell and string.interpolated.process-substitution.shell
TokenRule.string("`(?:\\\\.|[^`\\\\])*`"),
TokenRule.string("[><]\\([^)]*\\)"),
// constant.character.escape.line-continuation.shell, then constant.character.escape.shell
TokenRule.constant("\\\\(?=\\n)"),
TokenRule.constant("\\\\."),
// #normal_assignment_statement. Has to stay ahead of the command-name rule or `FOO=bar`
// reads as a command. Ours: (?<![\w-])(?!-) keeps `--flag=value` out.
TokenRule(
"((?<![\\w-])(?!-)[a-zA-Z_0-9-]+(?!\\w))(?:(\\[)[^\\[\\]]*(\\]))?(\\+=|-=|=)",
mapOf(1 to TokenType.BUILTIN, 4 to TokenType.OPERATOR),
),
// #floating_keyword — keyword.control.$0.shell
TokenRule.keyword("(?m)(?<=^|[;& \\t])(?:then|elif|else|done|end|do|if|fi)(?=[ \\t;&]|$)"),
// #for_statement — keyword.control.for.shell, variable.other.for.shell, keyword.control.in
TokenRule(
"(\\bfor\\b)[\\t ]*+((?<!\\w)[a-zA-Z_0-9-]+(?!\\w))[\\t ]*+(\\bin\\b)",
mapOf(1 to TokenType.KEYWORD, 2 to TokenType.BUILTIN, 3 to TokenType.KEYWORD),
),
TokenRule.keyword("\\bfor\\b"),
// #while_statement — keyword.control.while.shell
TokenRule.keyword("\\bwhile\\b"),
// #loop — the while/until, select and if begins, plus the done and fi ends
TokenRule.keyword("(?<=^|[;&\\s])(?:while|until)(?=[\\s;&]|$)"),
TokenRule(
"(?<=^|[;&\\s])(select)\\s+((?:[^\\s\\\\]|\\\\.)+)(?=[\\s;&]|$)",
mapOf(1 to TokenType.KEYWORD, 2 to TokenType.BUILTIN),
),
TokenRule.keyword("(?<=^|[;&\\s])if(?=[\\s;&]|$)"),
TokenRule.keyword("(?<=^|[;&\\s])done(?=[\\s;&)]|$)"),
TokenRule.keyword("(?<=^|[;&\\s])fi(?=[\\s;&]|$)"),
// #case_statement — keyword.control.case.shell, keyword.control.in, keyword.control.esac
TokenRule(
"(\\bcase\\b)[\\t ]*+.+?[\\t ]*+(\\bin\\b)",
mapOf(1 to TokenType.KEYWORD, 2 to TokenType.KEYWORD),
),
TokenRule.keyword("\\besac\\b"),
// #pipeline — keyword.other.shell
TokenRule.keyword("(?<=^|[;&\\s])time(?=[\\s;&]|$)"),
// #modified_assignment_statement — storage.modifier.$0.shell
TokenRule.keyword("(?m)(?<=^|[;&\\t ])(?:readonly|declare|typeset|export|local)(?=[\\t ;&]|$)"),
// #function_definition — storage.type.function.shell and entity.name.function.shell
TokenRule.functionDeclaration("[\\t ]*+(\\bfunction\\b)[\\t ]*+([^ \\t\\n\\r()=\"']+)"),
TokenRule.functionCall("[\\t ]*+([^ \\t\\n\\r()=\"']+)[\\t ]*+\\([\\t ]*+\\)"),
// #command_name_range, in the bundle's order: control-flow commands, builtins, #variable,
// then any other word.
TokenRule(COMMAND_START + "((?:continue|return|break)(?!\\w))", mapOf(1 to TokenType.KEYWORD)),
TokenRule(
COMMAND_START +
"((?:unfunction|continue|autoload|unsetopt|bindkey|builtin|getopts|command|declare|" +
"unalias|history|unlimit|typeset|suspend|source|printf|unhash|disown|ulimit|return|" +
"which|alias|break|false|print|shift|times|umask|umask|unset|read|type|exec|eval|" +
"wait|echo|dirs|jobs|kill|hash|stat|exit|test|trap|true|let|set|pwd|cd|fg|bg|fc|:|" +
"\\.)(?!\\/)(?!\\w)(?!-))",
mapOf(1 to TokenType.BUILTIN),
),
// #variable — ${...} before $name, so the braced form wins the tie at the $
TokenRule.builtin("\\$\\{[^{}]*\\}"),
TokenRule.builtin("\\$@(?!\\w)"),
TokenRule.builtin("\\$[0-9](?!\\w)"),
TokenRule.builtin("\\$[-*#?$!0_](?!\\w)"),
TokenRule.builtin("\\$\\w+(?!\\w)"),
// entity.name.function.call.shell entity.name.command.shell
TokenRule.functionCall(
COMMAND_START + NOT_COMMAND_CHAR + NOT_KEYWORD + "([^ \\n\\t\\r\"'=;&\\|`\\)\\{<>]+)"
),
// #support — support.function.builtin.shell for the no-op and the dot command
TokenRule.builtin("(?<=^|[;&\\s])[:.](?=[\\s;&]|$)"),
// constant.language.$0.shell, from #boolean. After the builtin rule, so a bare `true`
// command still reads as a builtin, as in the bundle.
TokenRule.constant("\\b(?:true|false)\\b"),
// #option — constant.other.option.shell, with the begin's guard and the end fused on
TokenRule(
"[\\t ]++(-(?![!&|(){\\[<>#;\\t\\n ]|$)[^\\t \\n;|&)`}\\]]*)",
mapOf(1 to TokenType.CONSTANT),
),
// #pipeline's keyword.operator.pipe.shell, then #redirection and #redirect_number
TokenRule.operator("[|!]"),
TokenRule.operator("<<<"),
TokenRule.operator("(?<![<>])(?:&>|\\d*>&\\d*|\\d*(?:>>|>|<)|\\d*<&|\\d*<>)(?![<>])"),
TokenRule.operator("(?<=[\\t ])\\d+(?=>)"),
// #pathname — keyword.operator.tilde.shell
TokenRule.operator("(?m)(?<=\\s|:|=|^)~"),
// #numeric_literal — constant.numeric.shell
TokenRule("(?m)(?<==| |\\t|^|\\{|\\(|\\[)$NUMBER(?= |\\t|$|\\}|\\)|;)", NUMBER_GROUPS),
),
)
@@ -0,0 +1,125 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.code.highlighting.languages
import org.jetbrains.jewel.intui.standalone.code.highlighting.LanguageGrammar
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenRule
import org.jetbrains.jewel.intui.standalone.code.highlighting.TokenType
// Patterns ported from plugins/textmate/lib/bundles/yaml/syntaxes/yaml-1.2.tmLanguage.json.
//
// Differences you can see:
// - Plain scalars in value position stay unstyled, so `value` in `key: value` is plain where TextMate
// colors it as a string. Only the tree tells the bundle it is a value and not a key.
// - Block scalars (`key: |`, `key: >`) stay unstyled for the same reason. The portable spelling of the
// header would color every `|` and `>` in the file.
// The end-of-plain-scalar lookahead every constant.* rule in the bundle carries.
private const val VALUE_END = "(?=[\\t ]++#|[\\t ]*+(?>[\\r\\n,\\]}]|:[\\r\\n\\t ,\\[\\]{}]|\\z))"
// c-indicator exclusion set: the first character of a plain scalar, from #block-map-key-plain.
private const val PLAIN_FIRST = "[\\x{85}[^-?:,\\[\\]{}#&*!|>'\"%@` \\p{Cntrl}\\p{Cs}\\x{FEFF}\\x{FFFE}\\x{FFFF}]]"
// Plain scalar body, from the #block-mapping begin lookahead.
private const val PLAIN_TAIL = "(?>[^:#\\r\\n]++|:(?![\\r\\n\\t ])|(?<! |\\t)#++)*+"
// A key ends at `[\t ]*:` followed by whitespace, from #block-map-key-plain's end pattern.
private const val KEY_END = "(?=[\\t ]*+:(?:[\\r\\n\\t ]|\\z))"
// Leading indentation plus an optional block-sequence marker, matched but not colored.
private const val KEY_INDENT = "(?m)^[ \\t]*+(?:-[ \\t]++)?+"
// KEY_INDENT anchors the two block-mapping rules to a line start, so a flow map written on one line needs
// its own pair. These three come from #flow-sequence-map-key's begin and #flow-key-plain-in's end: flow
// context is "after `{`, `[`, `,` or a space", and in flow the `,[]{}` characters also close a scalar.
private const val FLOW_KEY_BEHIND = "(?m)(?<=[\\t ,\\[{]|^)"
private const val FLOW_TAIL = "(?>[^:#,\\[\\]{}\\r\\n]++|:(?![\\r\\n\\t ,\\[\\]{}])|(?<! |\\t)#++)*+"
private const val FLOW_KEY_END = "(?=[\\t ]*+:(?:[\\r\\n\\t ,\\[\\]{}]|\\z))"
internal val YAML =
LanguageGrammar(
name = "yaml",
aliases =
listOf(
"cff",
"eyaml",
"eyml",
"mir",
"reek",
"rviz",
"sublime-syntax",
"syntax",
"winget",
"yaml.sed",
"yaml",
"yaml-tmlanguage",
"yml.mysql",
"yml",
),
rules =
listOf(
// comment.line.number-sign.yaml — the # must be preceded by whitespace or start of line,
// so `foo#bar` is a scalar rather than a comment
TokenRule.comment("(?m)(?<=[\\x{FEFF}\\t ]|^)#[^\\r\\n]*+"),
// meta.directives.yaml — keyword.other.directive.yaml.yaml on the name,
// constant.numeric.yaml-version.yaml on the version
TokenRule("(?m)^(%)(YAML)([\\t ]++)(1\\.[0-3])", mapOf(2 to TokenType.KEYWORD, 4 to TokenType.NUMBER)),
// keyword.other.directive.tag.yaml, from #directives
TokenRule("(?m)^(%)(TAG)(?>([\\t ]++)((!)(?>[0-9A-Za-z-]*+(!))?+))?+", mapOf(2 to TokenType.KEYWORD)),
// entity.other.document.begin.yaml / .end.yaml. No TokenType corresponds to
// entity.other.document, so KEYWORD is our choice rather than the bundle's.
TokenRule.keyword("(?m)^---(?=[\\r\\n\\t ]|\\z)"),
TokenRule.keyword("(?m)^\\.{3}(?=[\\r\\n\\t ]|\\z)"),
// meta.map.key.yaml string.quoted.{double,single}.yaml entity.name.tag.yaml
TokenRule(
KEY_INDENT + "(\"(?>[^\\\\\"]++|\\\\.)*+\"|'(?>[^']++|'')*+')" + KEY_END,
mapOf(1 to TokenType.PROPERTY_KEY),
),
// meta.map.key.yaml string.unquoted.plain.yaml entity.name.tag.yaml
TokenRule(
KEY_INDENT + "((?:" + PLAIN_FIRST + "|[?:-](?![\\r\\n\\t ]))" + PLAIN_TAIL + ")" + KEY_END,
mapOf(1 to TokenType.PROPERTY_KEY),
),
// The same two, for a flow map: meta.flow.map.key.yaml … entity.name.tag.yaml. Both stay
// ahead of the plain string rules so `{"foo": 1}` reads as a key rather than a string.
TokenRule(
FLOW_KEY_BEHIND + "(\"(?>[^\\\\\"]++|\\\\.)*+\"|'(?>[^']++|'')*+')" + FLOW_KEY_END,
mapOf(1 to TokenType.PROPERTY_KEY),
),
TokenRule(
FLOW_KEY_BEHIND +
"((?:" +
PLAIN_FIRST +
"|[?:-](?![\\r\\n\\t ,\\[\\]{}]))" +
FLOW_TAIL +
")" +
FLOW_KEY_END,
mapOf(1 to TokenType.PROPERTY_KEY),
),
// string.quoted.double.yaml / string.quoted.single.yaml
TokenRule.string("\"(?>[^\\\\\"]++|\\\\.)*+\""),
TokenRule.string("'(?>[^']++|'')*+'"),
// keyword.control.flow.anchor.yaml
TokenRule.keyword("&[\\x{85}[^ ,\\[\\]{}\\p{Cntrl}\\p{Cs}\\x{FEFF}\\x{FFFE}\\x{FFFF}]]++"),
// keyword.control.flow.alias.yaml
TokenRule.keyword("\\*[\\x{85}[^ ,\\[\\]{}\\p{Cntrl}\\p{Cs}\\x{FEFF}\\x{FFFE}\\x{FFFF}]]++"),
// storage.type.tag.verbatim.yaml, then storage.type.tag.shorthand.yaml, which also covers
// storage.type.tag.non-specific.yaml (a bare `!`)
TokenRule.keyword("!<[^>\\r\\n]*+>"),
TokenRule.keyword("![^\\r\\n\\t ,\\[\\]{}]*+"),
// constant.language.boolean.yaml — 1.2 knows only these six spellings; yes/no/on/off are
// YAML 1.1 and are deliberately absent from the bundle
TokenRule.constant("(?>true|True|TRUE|false|False|FALSE)$VALUE_END"),
// constant.language.null.yaml
TokenRule.constant("(?>null|Null|NULL|~)$VALUE_END"),
// constant.numeric.*.yaml — inf/nan before float, and radix before decimal, so the more
// specific rule wins the tie at the same offset
TokenRule.number("[+-]?+\\.(?>inf|Inf|INF)$VALUE_END"),
TokenRule.number("\\.(?>nan|NaN|NAN)$VALUE_END"),
TokenRule.number("0x[0-9a-fA-F]++$VALUE_END"),
TokenRule.number("0o[0-7]++$VALUE_END"),
TokenRule.number("[+-]?+(?>\\.[0-9]++|[0-9]++(?>\\.[0-9]*+)?+)(?>[eE][+-]?+[0-9]++)?+$VALUE_END"),
TokenRule.number("[+-]?+[0-9]++$VALUE_END"),
),
)
+4 -4
View File
@@ -3708,7 +3708,7 @@ package org.jetbrains.jewel.ui.icons {
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Add;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey AddJdk;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Add_20x20;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Alpha;
field @Deprecated @SuppressCompatibility @org.jetbrains.annotations.ApiStatus.ScheduledForRemoval public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Alpha;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ArrowDown;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ArrowDownSmall;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ArrowLeft;
@@ -3723,7 +3723,7 @@ package org.jetbrains.jewel.ui.icons {
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey BalloonInformation;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey BalloonWarning;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey BalloonWarning12;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Beta;
field @Deprecated @SuppressCompatibility @org.jetbrains.annotations.ApiStatus.ScheduledForRemoval public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Beta;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ButtonDropTriangle;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ChevronDown;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ChevronDownLargeWhite;
@@ -3809,7 +3809,7 @@ package org.jetbrains.jewel.ui.icons {
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ModifiedSelected;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey MoreTabs;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Mouse;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey New_badge;
field @Deprecated @SuppressCompatibility @org.jetbrains.annotations.ApiStatus.ScheduledForRemoval public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey New_badge;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Note;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey NotificationError;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey NotificationInfo;
@@ -3854,7 +3854,7 @@ package org.jetbrains.jewel.ui.icons {
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Tree;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey TreeHovered;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey TreeSelected;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey TrialBadge;
field @Deprecated @SuppressCompatibility @org.jetbrains.annotations.ApiStatus.ScheduledForRemoval public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey TrialBadge;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey User;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Vcs;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Warning;
@@ -3574,7 +3574,7 @@ package org.jetbrains.jewel.ui.icons {
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Add;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey AddJdk;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Add_20x20;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Alpha;
field @Deprecated @SuppressCompatibility @org.jetbrains.annotations.ApiStatus.ScheduledForRemoval public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Alpha;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ArrowDown;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ArrowDownSmall;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ArrowLeft;
@@ -3589,7 +3589,7 @@ package org.jetbrains.jewel.ui.icons {
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey BalloonInformation;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey BalloonWarning;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey BalloonWarning12;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Beta;
field @Deprecated @SuppressCompatibility @org.jetbrains.annotations.ApiStatus.ScheduledForRemoval public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Beta;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ButtonDropTriangle;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ChevronDown;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ChevronDownLargeWhite;
@@ -3675,7 +3675,7 @@ package org.jetbrains.jewel.ui.icons {
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey ModifiedSelected;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey MoreTabs;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Mouse;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey New_badge;
field @Deprecated @SuppressCompatibility @org.jetbrains.annotations.ApiStatus.ScheduledForRemoval public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey New_badge;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Note;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey NotificationError;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey NotificationInfo;
@@ -3720,7 +3720,7 @@ package org.jetbrains.jewel.ui.icons {
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Tree;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey TreeHovered;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey TreeSelected;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey TrialBadge;
field @Deprecated @SuppressCompatibility @org.jetbrains.annotations.ApiStatus.ScheduledForRemoval public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey TrialBadge;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey User;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Vcs;
field @SuppressCompatibility public static final org.jetbrains.jewel.ui.icon.IntelliJIconKey Warning;