alacritty: normalize key binding escape sequences

`pkgs.formats.toml` escapes backslashes and cannot emit raw control
characters, so key-binding `chars` written as escape text (e.g.
"\uXXXX" or the "\^[" caret form) ended up as invalid literals in the
generated config. Rewrite each escape to a placeholder before
generation and restore it to "\u" afterward.

Use `builtins.split` so escapes are handled anywhere in the value,
including when repeated or mixed with other text.
This commit is contained in:
Austin Horstman 2026-06-24 21:18:59 -05:00
parent 085e92b04a
commit 3d8f49f360
4 changed files with 121 additions and 21 deletions

View file

@ -96,27 +96,37 @@ in
};
xdg.configFile."alacritty/alacritty.toml" = lib.mkIf (cfg.settings != { }) {
source = (tomlFormat.generate "alacritty.toml" cfg.settings).overrideAttrs (
_finalAttrs: prevAttrs: {
buildCommand = lib.concatStringsSep "\n" [
prevAttrs.buildCommand
# Nix cannot spell TOML escape sequences like "\u001d" directly:
# "\\u001d" is a literal backslash-u string, while fromJSON creates
# a control character and fails for "\u0000". Normalize both TOML
# generator outputs to the Alacritty escape form:
# chars = "\\u001d" -> chars = "\u001d"
# chars = '\u001d' -> chars = "\u001d"
''
sed \
-E \
-e "s/= \"\\\\\\\\u([0-9a-fA-F]{4})\"\$/= \"\\\\u\1\"/" \
-e "s/= '\\\\u([0-9a-fA-F]{4})'\$/= \"\\\\u\1\"/" \
"$out" > "$TMPDIR/alacritty.toml"
cp "$TMPDIR/alacritty.toml" "$out"
''
];
}
);
source =
let
# `pkgs.formats.toml` escapes backslashes and cannot emit raw
# control characters (NUL cannot even appear in a Nix string), so a
# `chars` escape like "\u001d" generates as literal "\\u001d",
# which Alacritty rejects. Stash each escape behind a placeholder that
# survives generation, then restore "\u" in the build command.
unicodeEscapePrefix = "__home_manager_alacritty_unicode_escape_";
normalizeAlacrittyEscapes =
value:
if builtins.isAttrs value then
lib.mapAttrs (_: normalizeAlacrittyEscapes) value
else if builtins.isList value then
map normalizeAlacrittyEscapes value
else if !builtins.isString value then
value
else
# Normalize the "\^[" caret form to "\u001b", then replace every
# "\uXXXX" with the placeholder.
lib.concatMapStrings (
chunk: if builtins.isList chunk then unicodeEscapePrefix + builtins.head chunk else chunk
) (builtins.split "\\\\u([0-9a-fA-F]{4})" (builtins.replaceStrings [ "\\^[" ] [ "\\u001b" ] value));
in
(tomlFormat.generate "alacritty.toml" (normalizeAlacrittyEscapes cfg.settings)).overrideAttrs
(prevAttrs: {
buildCommand = lib.concatStringsSep "\n" [
prevAttrs.buildCommand
''substituteInPlace $out --replace-quiet '${unicodeEscapePrefix}' '\u' ''
];
});
};
};
}

View file

@ -20,6 +20,36 @@
mods = "Alt";
chars = "\\u001d";
}
{
key = "Back";
mods = "Shift";
chars = "\\u001b[3~";
}
{
key = "Delete";
mods = "Shift";
chars = "\\^[[3~";
}
{
key = "A";
mods = "Control";
chars = "\\u0000";
}
{
key = "Up";
mods = "Control";
chars = "\\u001b\\u001b";
}
{
key = "Down";
mods = "Control";
chars = "OK\\u001b";
}
{
key = "Left";
mods = "Control";
chars = "\\^[\\^[";
}
];
font =

View file

@ -14,6 +14,36 @@ chars = "\u001d"
key = "RBracket"
mods = "Alt"
[[keyboard.bindings]]
chars = "\u001b[3~"
key = "Back"
mods = "Shift"
[[keyboard.bindings]]
chars = "\u001b[3~"
key = "Delete"
mods = "Shift"
[[keyboard.bindings]]
chars = "\u0000"
key = "A"
mods = "Control"
[[keyboard.bindings]]
chars = "\u001b\u001b"
key = "Up"
mods = "Control"
[[keyboard.bindings]]
chars = "OK\u001b"
key = "Down"
mods = "Control"
[[keyboard.bindings]]
chars = "\u001b\u001b"
key = "Left"
mods = "Control"
[window.dimensions]
columns = 200
lines = 3

View file

@ -18,6 +18,36 @@
mods = "Alt";
chars = "\\u001d";
}
{
key = "Back";
mods = "Shift";
chars = "\\u001b[3~";
}
{
key = "Delete";
mods = "Shift";
chars = "\\^[[3~";
}
{
key = "A";
mods = "Control";
chars = "\\u0000";
}
{
key = "Up";
mods = "Control";
chars = "\\u001b\\u001b";
}
{
key = "Down";
mods = "Control";
chars = "OK\\u001b";
}
{
key = "Left";
mods = "Control";
chars = "\\^[\\^[";
}
];
font = {