Add Hyprlang lexer (#1286)

Adds a Hyprlang lexer for Hyprland configuration files.

Closes #1046.

The lexer covers common Hyprland config syntax including variables,
assignments, sections and device sections, exec/source directives,
colors, monitor dimensions, booleans, modifiers, and window-rule command
lists.

Test fixtures were generated for the lexer and analyser.

Validation run locally:

- `RECORD=true go test ./lexers`
- `go test ./lexers -count=1`
- `go test ./... -count=1`
- `./bin/golangci-lint run`
This commit is contained in:
Bryce D. Greybeard
2026-06-17 15:41:01 +10:00
committed by GitHub
parent a6d00fe2cd
commit f954f077eb
5 changed files with 379 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
<lexer>
<config>
<name>Hyprlang</name>
<alias>hyprlang</alias>
<alias>hypr</alias>
<filename>hypr*.conf</filename>
<filename>*.hypr</filename>
<mime_type>text/x-hyprlang</mime_type>
<analyse>
<regex pattern="(?m)^\s*(bind|bindm|monitor|workspace|windowrulev2?|exec(?:-once|-shutdown)?|execr(?:-once)?|source)\s*=" score="0.6"/>
<regex pattern="(?m)^\s*\$[A-Za-z_]\w*\s*=" score="0.4"/>
<regex pattern="(?m)^\s*(general|decoration|animations|input|device|binds|gestures|misc|master|dwindle|xwayland|opengl|render|ecosystem)\s*(?::[^\s{]+)?\s*\{" score="0.4"/>
</analyse>
</config>
<rules>
<state name="root">
<rule pattern="\s+">
<token type="TextWhitespace"/>
</rule>
<rule pattern="#.*">
<token type="CommentSingle"/>
</rule>
<rule pattern="(\$[A-Za-z_]\w*)(\s*)(=)">
<bygroups>
<token type="NameVariable"/>
<token type="TextWhitespace"/>
<token type="Operator"/>
</bygroups>
<push state="value"/>
</rule>
<rule pattern="(source|exec-once|execr-once|exec-shutdown|exec|execr)(\s*)(=)">
<bygroups>
<token type="KeywordReserved"/>
<token type="TextWhitespace"/>
<token type="Operator"/>
</bygroups>
<push state="value"/>
</rule>
<rule pattern="([A-Za-z_][\w\d.\-]*)(\s*)(:)(\s*)([\w\d][\w\d/.\-:]*)(\s*)(\{)">
<bygroups>
<token type="NameNamespace"/>
<token type="TextWhitespace"/>
<token type="Punctuation"/>
<token type="TextWhitespace"/>
<token type="NameClass"/>
<token type="TextWhitespace"/>
<token type="Punctuation"/>
</bygroups>
</rule>
<rule pattern="([A-Za-z_][\w\d.\-]*)(\s*)(\{)">
<bygroups>
<token type="NameNamespace"/>
<token type="TextWhitespace"/>
<token type="Punctuation"/>
</bygroups>
</rule>
<rule pattern="([A-Za-z_][\w\d.\-]*)(\s*)(=)">
<bygroups>
<token type="NameAttribute"/>
<token type="TextWhitespace"/>
<token type="Operator"/>
</bygroups>
<push state="value"/>
</rule>
<rule pattern="[{}]">
<token type="Punctuation"/>
</rule>
<rule>
<include state="value_atoms"/>
</rule>
</state>
<state name="value">
<rule pattern="\n">
<token type="TextWhitespace"/>
<pop depth="1"/>
</rule>
<rule>
<include state="value_atoms"/>
</rule>
</state>
<state name="value_atoms">
<rule pattern="[ \t]+">
<token type="TextWhitespace"/>
</rule>
<rule pattern="#.*">
<token type="CommentSingle"/>
</rule>
<rule pattern="(rgb|rgba)(\()">
<bygroups>
<token type="NameBuiltin"/>
<token type="Punctuation"/>
</bygroups>
</rule>
<rule pattern="\)">
<token type="Punctuation"/>
</rule>
<rule pattern="0x[0-9A-Fa-f]{6,8}\b">
<token type="LiteralNumberHex"/>
</rule>
<rule pattern="\b[0-9A-Fa-f]{6,8}\b">
<token type="LiteralNumberHex"/>
</rule>
<rule pattern="\b[+-]?\d+(?:\.\d+)?x[+-]?\d+(?:\.\d+)?(?:@[+-]?\d+(?:\.\d+)?)?\b">
<token type="LiteralNumberFloat"/>
</rule>
<rule pattern="\b[0-9]{1,3}deg\b">
<token type="LiteralNumberFloat"/>
</rule>
<rule pattern="[+-]?(?:\d+\.\d+|\d+)%?(?:@[+-]?(?:\d+\.\d+|\d+))?">
<token type="LiteralNumberFloat"/>
</rule>
<rule pattern="\$[A-Za-z_]\w*">
<token type="NameVariable"/>
</rule>
<rule pattern="\b(true|false|on|off|yes|no|null)\b">
<token type="KeywordConstant"/>
</rule>
<rule pattern="\b(SHIFT|CAPS|CTRL|CONTROL|ALT|ALT_L|MOD2|MOD3|SUPER|WIN|LOGO|MOD4|MOD5|TAB)\b">
<token type="NameConstant"/>
</rule>
<rule pattern="\b(floating|fullscreen|pinned|activewindow|activeworkspace|class|title|initialClass|initialTitle|tag|xwayland|workspace|monitor|float|tile|move|size|center|opacity|animation|border|rounding|noanim|noblur|nofocus|stayfocused|suppressevent|plugin)\b">
<token type="NameFunction"/>
</rule>
<rule pattern="[\[\](),;]">
<token type="Punctuation"/>
</rule>
<rule pattern="[-+*/]">
<token type="Operator"/>
</rule>
<rule pattern="[^\s#\[\](),;]+">
<token type="LiteralString"/>
</rule>
</state>
</rules>
</lexer>
+6
View File
@@ -0,0 +1,6 @@
$mainMod = SUPER
monitor=DP-1,1378x1024@60,0x0,1
exec-once = waybar
input {
kb_layout = us
}
+1
View File
@@ -0,0 +1 @@
1
+33
View File
@@ -0,0 +1,33 @@
# Hyprland configuration sample
$mainMod = SUPER
$terminal = kitty
x = 1
source = ~/.config/hypr/env.conf
monitor=DP-1,1378x1024@60,0x0,1
exec-once = hyprpaper & waybar
exec = [monitor 1; float; move 20% 30%] kitty
execr = notify-send Hyprland ready
input {
kb_layout = us
follow_mouse = 1
}
section:device {
name = epic-mouse-v1
sensitivity = -0.5
enabled = true
}
decoration {
rounding = 8
col.shadow = rgba(1a1a1aee)
col.active_border = rgb(33ccff) rgb(ff00cc) 45deg
}
bind = $mainMod, Return, exec, $terminal
bindm = $mainMod, mouse:272, movewindow
windowrulev2 = float, class:^(pavucontrol)$
+202
View File
@@ -0,0 +1,202 @@
[
{"type":"CommentSingle","value":"# Hyprland configuration sample"},
{"type":"TextWhitespace","value":"\n"},
{"type":"NameVariable","value":"$mainMod"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"NameConstant","value":"SUPER"},
{"type":"TextWhitespace","value":"\n"},
{"type":"NameVariable","value":"$terminal"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"kitty"},
{"type":"TextWhitespace","value":"\n"},
{"type":"NameAttribute","value":"x"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralNumberFloat","value":"1"},
{"type":"TextWhitespace","value":"\n\n"},
{"type":"KeywordReserved","value":"source"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"~/.config/hypr/env.conf"},
{"type":"TextWhitespace","value":"\n\n"},
{"type":"NameAttribute","value":"monitor"},
{"type":"Operator","value":"="},
{"type":"LiteralString","value":"DP-1"},
{"type":"Punctuation","value":","},
{"type":"LiteralNumberFloat","value":"1378x1024@60"},
{"type":"Punctuation","value":","},
{"type":"LiteralNumberFloat","value":"0x0"},
{"type":"Punctuation","value":","},
{"type":"LiteralNumberFloat","value":"1"},
{"type":"TextWhitespace","value":"\n\n"},
{"type":"KeywordReserved","value":"exec-once"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"hyprpaper"},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"\u0026"},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"waybar"},
{"type":"TextWhitespace","value":"\n"},
{"type":"KeywordReserved","value":"exec"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"Punctuation","value":"["},
{"type":"NameFunction","value":"monitor"},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralNumberFloat","value":"1"},
{"type":"Punctuation","value":";"},
{"type":"TextWhitespace","value":" "},
{"type":"NameFunction","value":"float"},
{"type":"Punctuation","value":";"},
{"type":"TextWhitespace","value":" "},
{"type":"NameFunction","value":"move"},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralNumberFloat","value":"20%"},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralNumberFloat","value":"30%"},
{"type":"Punctuation","value":"]"},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"kitty"},
{"type":"TextWhitespace","value":"\n"},
{"type":"KeywordReserved","value":"execr"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"notify-send"},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"Hyprland"},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"ready"},
{"type":"TextWhitespace","value":"\n\n"},
{"type":"NameNamespace","value":"input"},
{"type":"TextWhitespace","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"TextWhitespace","value":"\n "},
{"type":"NameAttribute","value":"kb_layout"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"us"},
{"type":"TextWhitespace","value":"\n "},
{"type":"NameAttribute","value":"follow_mouse"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralNumberFloat","value":"1"},
{"type":"TextWhitespace","value":"\n"},
{"type":"Punctuation","value":"}"},
{"type":"TextWhitespace","value":"\n\n"},
{"type":"NameNamespace","value":"section"},
{"type":"Punctuation","value":":"},
{"type":"NameClass","value":"device"},
{"type":"TextWhitespace","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"TextWhitespace","value":"\n "},
{"type":"NameAttribute","value":"name"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"epic-mouse-v1"},
{"type":"TextWhitespace","value":"\n "},
{"type":"NameAttribute","value":"sensitivity"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralNumberFloat","value":"-0.5"},
{"type":"TextWhitespace","value":"\n "},
{"type":"NameAttribute","value":"enabled"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"KeywordConstant","value":"true"},
{"type":"TextWhitespace","value":"\n"},
{"type":"Punctuation","value":"}"},
{"type":"TextWhitespace","value":"\n\n"},
{"type":"NameNamespace","value":"decoration"},
{"type":"TextWhitespace","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"TextWhitespace","value":"\n "},
{"type":"NameAttribute","value":"rounding"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralNumberFloat","value":"8"},
{"type":"TextWhitespace","value":"\n "},
{"type":"NameAttribute","value":"col.shadow"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"NameBuiltin","value":"rgba"},
{"type":"Punctuation","value":"("},
{"type":"LiteralNumberHex","value":"1a1a1aee"},
{"type":"Punctuation","value":")"},
{"type":"TextWhitespace","value":"\n "},
{"type":"NameAttribute","value":"col.active_border"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"NameBuiltin","value":"rgb"},
{"type":"Punctuation","value":"("},
{"type":"LiteralNumberHex","value":"33ccff"},
{"type":"Punctuation","value":")"},
{"type":"TextWhitespace","value":" "},
{"type":"NameBuiltin","value":"rgb"},
{"type":"Punctuation","value":"("},
{"type":"LiteralNumberHex","value":"ff00cc"},
{"type":"Punctuation","value":")"},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralNumberFloat","value":"45deg"},
{"type":"TextWhitespace","value":"\n"},
{"type":"Punctuation","value":"}"},
{"type":"TextWhitespace","value":"\n\n"},
{"type":"NameAttribute","value":"bind"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"NameVariable","value":"$mainMod"},
{"type":"Punctuation","value":","},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"Return"},
{"type":"Punctuation","value":","},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"exec"},
{"type":"Punctuation","value":","},
{"type":"TextWhitespace","value":" "},
{"type":"NameVariable","value":"$terminal"},
{"type":"TextWhitespace","value":"\n"},
{"type":"NameAttribute","value":"bindm"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"NameVariable","value":"$mainMod"},
{"type":"Punctuation","value":","},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"mouse:272"},
{"type":"Punctuation","value":","},
{"type":"TextWhitespace","value":" "},
{"type":"LiteralString","value":"movewindow"},
{"type":"TextWhitespace","value":"\n"},
{"type":"NameAttribute","value":"windowrulev2"},
{"type":"TextWhitespace","value":" "},
{"type":"Operator","value":"="},
{"type":"TextWhitespace","value":" "},
{"type":"NameFunction","value":"float"},
{"type":"Punctuation","value":","},
{"type":"TextWhitespace","value":" "},
{"type":"NameFunction","value":"class"},
{"type":"LiteralString","value":":^"},
{"type":"Punctuation","value":"("},
{"type":"LiteralString","value":"pavucontrol"},
{"type":"Punctuation","value":")"},
{"type":"LiteralString","value":"$"},
{"type":"TextWhitespace","value":"\n"}
]