mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[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:
committed by
intellij-monorepo-bot
parent
e40150c5fe
commit
b0fd16a790
@@ -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.
|
||||
|
||||
+30
@@ -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" },
|
||||
)
|
||||
}
|
||||
}
|
||||
+2
@@ -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. */
|
||||
|
||||
+26
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+14
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+223
@@ -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))
|
||||
}
|
||||
}
|
||||
+319
@@ -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))
|
||||
}
|
||||
}
|
||||
+194
@@ -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(" ", "&", " ", " ", " ")) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
+181
@@ -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))
|
||||
}
|
||||
}
|
||||
+152
@@ -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(" ", " ", " ")) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -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")) {
|
||||
|
||||
+261
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
+169
@@ -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))
|
||||
}
|
||||
}
|
||||
+207
@@ -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)) }
|
||||
}
|
||||
+270
@@ -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))
|
||||
}
|
||||
}
|
||||
+176
@@ -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;
|
||||
}
|
||||
|
||||
+18
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+24
-4
@@ -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
|
||||
|
||||
+50
-30
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+28
-6
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -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,
|
||||
}
|
||||
|
||||
+235
@@ -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])[+-])*"),
|
||||
),
|
||||
)
|
||||
+413
@@ -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("\\*"),
|
||||
),
|
||||
)
|
||||
+169
@@ -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]+(;)"),
|
||||
),
|
||||
)
|
||||
+61
@@ -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+)?"),
|
||||
),
|
||||
)
|
||||
+29
@@ -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)
|
||||
+6
-3
@@ -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
|
||||
|
||||
+98
@@ -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\$])"
|
||||
),
|
||||
),
|
||||
)
|
||||
+2
-2
@@ -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"
|
||||
),
|
||||
|
||||
+115
@@ -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"""),
|
||||
),
|
||||
)
|
||||
+390
@@ -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),
|
||||
),
|
||||
)
|
||||
+196
@@ -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),
|
||||
),
|
||||
)
|
||||
+125
@@ -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"),
|
||||
),
|
||||
)
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user