start revive

This commit is contained in:
2026-07-30 21:25:03 -04:00
parent b7c4938c54
commit d8f11040bc
25 changed files with 5344 additions and 2559 deletions

View File

@@ -1,90 +1,121 @@
/**
* Keybinds persistence via JSONC file in XDG_CONFIG_HOME
*
* Handles copying keybind.jsonc from package to user config directory
* Handles copying keybinds.jsonc from package to user config directory
* and loading/saving keybind configurations.
*/
import { copyFile, mkdir } from "fs/promises";
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",
"keybind.jsonc",
process.cwd(),
"src",
"config",
"keybinds.jsonc",
);
const KEYBINDS_FILE = "keybinds.jsonc";
/** Default keybinds from package */
/** 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 = {
up: ["up", "k"],
down: ["down", "j"],
left: ["left", "h"],
right: ["right", "l"],
cycle: ["tab"],
dive: ["return"],
select: ["return"],
out: ["esc"],
inverseModifier: "shift",
leader: ":",
quit: ["<leader>q"],
"audio-toggle": ["<leader>p"],
"audio-pause": [],
"audio-play": [],
"audio-next": ["<leader>n"],
"audio-prev": ["<leader>l"],
"audio-seek-forward": ["<leader>sf"],
"audio-seek-backward": ["<leader>sb"],
// 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 / help / quit
command: [":"],
quit: ["q", "ctrl-c"],
help: ["~", "f1"],
// list ops
search: ["s"],
filter: ["f"],
sort: [","],
"toggle-hidden": ["."],
refresh: ["r"],
// audio transport (preserved; shifted single keys, no collisions)
"audio-toggle": ["P"],
"audio-next": ["N"],
"audio-prev": ["B"],
"audio-seek-forward": ["shift-."],
"audio-seek-backward": ["shift-,"],
};
/** Copy keybind.jsonc to user config directory on first run */
/** Copy keybinds.jsonc to user config directory on first run */
export async function copyKeybindsIfNeeded(): Promise<void> {
try {
const targetPath = getConfigFilePath(KEYBINDS_FILE);
try {
const targetPath = getConfigFilePath(KEYBINDS_FILE);
// Check if file already exists
const targetFile = Bun.file(targetPath);
if (await targetFile.exists()) return;
// Check if file already exists
const targetFile = Bun.file(targetPath);
if (await targetFile.exists()) return;
await ensureConfigDir();
await copyFile(KEYBINDS_SOURCE, targetPath);
} catch {
// Silently ignore errors
}
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);
try {
const filePath = getConfigFilePath(KEYBINDS_FILE);
const file = Bun.file(filePath);
if (!(await file.exists())) return DEFAULT_KEYBINDS;
if (!(await file.exists())) return DEFAULT_KEYBINDS;
const raw = await file.text();
const parsed = parseJSONC(raw);
const raw = await file.text();
const parsed = parseJSONC(raw);
if (!parsed || typeof parsed !== "object") return DEFAULT_KEYBINDS;
if (!parsed || typeof parsed !== "object") return DEFAULT_KEYBINDS;
return { ...DEFAULT_KEYBINDS, ...parsed } as KeybindsResolved;
} catch {
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,
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
}
try {
await ensureConfigDir();
const filePath = getConfigFilePath(KEYBINDS_FILE);
await Bun.write(filePath, JSON.stringify(keybinds, null, 2));
} catch {
// Silently ignore write errors
}
}