Files
PodTui/src/utils/keybinds-persistence.ts

130 lines
3.5 KiB
TypeScript

/**
* Keybinds persistence via JSONC file in XDG_CONFIG_HOME
*
* Handles copying keybinds.jsonc from package to user config directory
* and loading/saving keybind configurations.
*/
import { copyFile } from "fs/promises";
import path from "path";
import { parseJSONC } from "./jsonc";
import { getConfigFilePath, ensureConfigDir } from "./config-dir";
import type { KeybindsResolved } from "../context/KeybindContext";
const KEYBINDS_SOURCE = path.join(
process.cwd(),
"src",
"config",
"keybinds.jsonc",
);
const KEYBINDS_FILE = "keybinds.jsonc";
/** Default keybinds (yazi-style) — mirrors src/config/keybinds.jsonc so the
* app works before a user keybinds file is copied into place. */
const DEFAULT_KEYBINDS: KeybindsResolved = {
// movement
"move-down": ["j", "down"],
"move-up": ["k", "up"],
"page-down": ["ctrl-d"],
"page-up": ["ctrl-u"],
"full-down": ["ctrl-f"],
"full-up": ["ctrl-b"],
"jump-down": ["J"],
"jump-up": ["K"],
"goto-top": [["g", "g"]],
"goto-bottom": ["G"],
// pane swipe
"swipe-prev": ["h", "left"],
"swipe-next": ["l", "right"],
// open / select
open: ["return", "enter"],
"open-interactive": ["shift-return"],
"toggle-select": ["space"],
"visual-mode": ["v"],
"toggle-all": ["ctrl-a"],
"invert-all": ["ctrl-r"],
escape: ["escape", "ctrl-["],
// tabs
"tab-prev": ["["],
"tab-next": ["]"],
"tab-goto-1": ["1"],
"tab-goto-2": ["2"],
"tab-goto-3": ["3"],
"tab-goto-4": ["4"],
"tab-goto-5": ["5"],
"tab-goto-6": ["6"],
// command palette / help / quit
// q opens the palette (type q + Enter to quit there); Q is the quick quit.
command: [":", "q"],
quit: ["Q", "ctrl-c"],
help: ["~", "f1"],
// list ops
search: ["s"],
"search-scope-toggle": ["tab"],
filter: ["f"],
sort: [","],
"toggle-hidden": ["."],
refresh: ["r"],
// a subscribes the focused show/episode result in place (x unsubscribes)
subscribe: ["a"],
unsubscribe: ["x"],
// downloads
download: ["d"],
"delete-download": ["D"],
"whitelist-toggle": ["w"],
// audio transport (preserved; shifted single keys, no collisions)
"audio-toggle": ["P"],
"audio-next": ["N"],
"audio-prev": ["B"],
"audio-seek-forward": ["shift-."], // > = shift+.
"audio-seek-backward": ["shift-,"], // < = shift+,
};
/** Copy keybinds.jsonc to user config directory on first run */
export async function copyKeybindsIfNeeded(): Promise<void> {
try {
const targetPath = getConfigFilePath(KEYBINDS_FILE);
const targetFile = Bun.file(targetPath);
if (await targetFile.exists()) return;
await ensureConfigDir();
await copyFile(KEYBINDS_SOURCE, targetPath);
} catch {
// Silently ignore errors
}
}
/** Load keybinds from JSONC file */
export async function loadKeybindsFromFile(): Promise<KeybindsResolved> {
try {
const filePath = getConfigFilePath(KEYBINDS_FILE);
const file = Bun.file(filePath);
if (!(await file.exists())) return DEFAULT_KEYBINDS;
const raw = await file.text();
const parsed = parseJSONC(raw);
if (!parsed || typeof parsed !== "object") return DEFAULT_KEYBINDS;
// Merge so partial user configs inherit defaults for missing keys.
return { ...DEFAULT_KEYBINDS, ...parsed } as KeybindsResolved;
} catch {
return DEFAULT_KEYBINDS;
}
}
/** Save keybinds to JSONC file */
export async function saveKeybindsToFile(
keybinds: KeybindsResolved,
): Promise<void> {
try {
await ensureConfigDir();
const filePath = getConfigFilePath(KEYBINDS_FILE);
await Bun.write(filePath, JSON.stringify(keybinds, null, 2));
} catch {
// Silently ignore write errors
}
}