import { execFile } from "node:child_process"; import { parseCanonicalKeymap } from "./canonical-parser.js"; import type { ResolvedReadlineProfile } from "./profile-loader.js"; import { parseReadlineVariables } from "./variables.js"; const INSERT_MARKER = "__PI_VI_INSERT__"; const VARIABLES_MARKER = "__PI_VI_VARIABLES__"; export type BashRunner = ( executable: string, arguments_: readonly string[], ) => Promise<{ stdout: string }>; const defaultRunner: BashRunner = (executable, arguments_) => new Promise((resolve, reject) => { execFile( executable, [...arguments_], { encoding: "utf8", env: { ...process.env, BASH_ENV: "", ENV: "" }, maxBuffer: 4 * 1024 * 1024, timeout: 5000, }, (error, stdout) => { if (error !== null) reject(new Error(error.message, { cause: error })); else resolve({ stdout }); }, ); }); /** Ask Bash/Readline to resolve includes, conditionals, variables, and macros. */ export async function loadBashResolvedInputrc( base: ResolvedReadlineProfile, inputrcPath: string, run: BashRunner = defaultRunner, ): Promise { const script = [ "set -o vi", 'bind -f "$1"', "bind -m vi-command -p", `printf '\\n${INSERT_MARKER}\\n'`, "bind -m vi-insertion -p", `printf '\\n${VARIABLES_MARKER}\\n'`, "bind -v", ].join("; "); const { stdout } = await run(base.metadata.bash.executable, [ "--noprofile", "--norc", "-c", script, "pi-vi", inputrcPath, ]); const insertMarker = `\n${INSERT_MARKER}\n`; const variablesMarker = `\n${VARIABLES_MARKER}\n`; const insertIndex = stdout.indexOf(insertMarker); const variablesIndex = stdout.indexOf(variablesMarker); if (insertIndex < 0 || variablesIndex < insertIndex) throw new Error("Bash inputrc dump omitted a section marker"); return { ...base, viCommand: parseCanonicalKeymap("vi-command", stdout.slice(0, insertIndex)), viInsert: parseCanonicalKeymap( "vi-insert", stdout.slice(insertIndex + insertMarker.length, variablesIndex), ), variables: parseReadlineVariables( stdout.slice(variablesIndex + variablesMarker.length), ), }; }