fix: fix keybindings (#406)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2025-12-28 19:31:01 -08:00
committed by GitHub
parent c576392db0
commit aca3d88800
6 changed files with 259 additions and 12 deletions

View File

@@ -324,3 +324,153 @@ export function removeKeybindingForCurrentTerminal(): InstallResult {
return removeKeybinding(path);
}
// ============================================================================
// WezTerm keybinding support
// WezTerm has a bug where Delete key sends 0x08 (backspace) instead of ESC[3~
// when kitty keyboard protocol is enabled. This keybinding fixes it.
// WezTerm auto-reloads config, so the fix takes effect immediately.
// See: https://github.com/wez/wezterm/issues/3758
// ============================================================================
/**
* Check if running in WezTerm
*/
export function isWezTerm(): boolean {
return process.env.TERM_PROGRAM === "WezTerm";
}
/**
* Get WezTerm config path
*/
export function getWezTermConfigPath(): string {
// WezTerm looks for config in these locations (in order):
// 1. $WEZTERM_CONFIG_FILE
// 2. $XDG_CONFIG_HOME/wezterm/wezterm.lua
// 3. ~/.config/wezterm/wezterm.lua
// 4. ~/.wezterm.lua
if (process.env.WEZTERM_CONFIG_FILE) {
return process.env.WEZTERM_CONFIG_FILE;
}
const xdgConfig = process.env.XDG_CONFIG_HOME;
if (xdgConfig) {
const xdgPath = join(xdgConfig, "wezterm", "wezterm.lua");
if (existsSync(xdgPath)) return xdgPath;
}
const configPath = join(homedir(), ".config", "wezterm", "wezterm.lua");
if (existsSync(configPath)) return configPath;
// Default to ~/.wezterm.lua
return join(homedir(), ".wezterm.lua");
}
/**
* The Lua code to fix Delete key in WezTerm
*/
const WEZTERM_DELETE_FIX = `
-- Letta Code: Fix Delete key sending wrong sequence with kitty keyboard protocol
-- See: https://github.com/wez/wezterm/issues/3758
local wezterm = require 'wezterm'
local keys = config.keys or {}
table.insert(keys, {
key = 'Delete',
mods = 'NONE',
action = wezterm.action.SendString '\\x1b[3~',
})
config.keys = keys
`;
/**
* Check if WezTerm config already has our Delete key fix
*/
export function wezTermDeleteFixExists(configPath: string): boolean {
if (!existsSync(configPath)) return false;
try {
const content = readFileSync(configPath, { encoding: "utf-8" });
// Check if our fix or equivalent already exists
return (
content.includes("Letta Code: Fix Delete key") ||
(content.includes("key = 'Delete'") &&
content.includes("SendString") &&
content.includes("\\x1b[3~"))
);
} catch {
return false;
}
}
/**
* Install WezTerm Delete key fix
*/
export function installWezTermDeleteFix(): InstallResult {
const configPath = getWezTermConfigPath();
try {
// Check if already installed
if (wezTermDeleteFixExists(configPath)) {
return { success: true, alreadyExists: true };
}
let content = "";
let backupPath: string | null = null;
if (existsSync(configPath)) {
backupPath = `${configPath}.letta-backup`;
copyFileSync(configPath, backupPath);
content = readFileSync(configPath, { encoding: "utf-8" });
}
// For simple configs that return a table directly, we need to modify them
// to use a config variable. Check if it's a simple "return {" style config.
if (content.includes("return {") && !content.includes("local config")) {
// Convert simple config to use config variable
content = content.replace(/return\s*\{/, "local config = {");
// Add return config at the end if not present
if (!content.includes("return config")) {
content = `${content.trimEnd()}\n\nreturn config\n`;
}
}
// If config doesn't exist or is empty, create a basic one
if (!content.trim()) {
content = `-- WezTerm configuration
local config = {}
return config
`;
}
// Insert our fix before "return config"
if (content.includes("return config")) {
content = content.replace(
"return config",
`${WEZTERM_DELETE_FIX}\nreturn config`,
);
} else {
// Append to end as fallback
content = `${content.trimEnd()}\n${WEZTERM_DELETE_FIX}\n`;
}
// Ensure parent directory exists
const parentDir = dirname(configPath);
if (!existsSync(parentDir)) {
mkdirSync(parentDir, { recursive: true });
}
writeFileSync(configPath, content, { encoding: "utf-8" });
return {
success: true,
backupPath: backupPath ?? undefined,
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
success: false,
error: `Failed to install WezTerm Delete key fix: ${message}`,
};
}
}