#!/usr/bin/env node // Display widths for node:readline's terminal mode (scr_readline_tty.c), // derived from the pinned Node release: getStringWidth (internal/util/inspect, // backed by ICU) of each code point on its own, and the Emoji_Presentation // and Emoji_Modifier code points, which getStringWidth skips after a ZWJ. // kGetDisplayPos measures one code point at a time and the completion // listing measures whole completions. // // node scripts/gen-readline-width-table.mjs regenerate the table // node scripts/gen-readline-width-table.mjs --check verify only import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { writeFile } from "node:fs/promises"; import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; import { join } from "node:path"; if (!process.execArgv.includes("--expose-internals")) { const self = fileURLToPath(import.meta.url); const run = spawnSync(process.execPath, ["--expose-internals", self, ...process.argv.slice(2)], { stdio: "inherit", }); process.exit(run.status ?? 1); } const root = fileURLToPath(new URL("..", import.meta.url)); const out = join(root, "packages/runtime/src/scr_readline_width_data.h"); const checkOnly = process.argv.includes("--check"); const pinned = readFileSync(join(root, ".node-version"), "utf8").trim(); if (process.version !== `v${pinned}`) { throw new Error(`run with Node v${pinned} (this is ${process.version})`); } const { getStringWidth } = createRequire(import.meta.url)("internal/util/inspect"); // [start, end, width] for the code points whose width is not 1 const widths = []; for (let cp = 0; cp <= 0x10ffff; cp++) { const w = getStringWidth(String.fromCodePoint(cp), false); const last = widths.at(-1); if (w === 1) continue; if (last && last[2] === w && last[1] === cp - 1) last[1] = cp; else widths.push([cp, cp, w]); } const emoji = []; const isEmoji = /^(?:\p{Emoji_Presentation}|\p{Emoji_Modifier})$/u; for (let cp = 0; cp <= 0x10ffff; cp++) { if (cp >= 0xd800 && cp <= 0xdfff) continue; if (!isEmoji.test(String.fromCodePoint(cp))) continue; const last = emoji.at(-1); if (last && last[1] === cp - 1) last[1] = cp; else emoji.push([cp, cp]); } const hex = (n) => `0x${n.toString(16)}`; const lines = [ `/* Generated by scripts/gen-readline-width-table.mjs from Node ${process.version}`, ` * (ICU ${process.versions.icu}, Unicode ${process.versions.unicode}). Do not edit. */`, "#ifndef SCR_READLINE_WIDTH_DATA_H", "#define SCR_READLINE_WIDTH_DATA_H", "/* getStringWidth(String.fromCodePoint(cp), false) where it is not 1 */", "typedef struct { uint32_t start, end; uint8_t width; } ScrRlWidthRange;", "static const ScrRlWidthRange scr_rl_width_ranges[] = {", ...widths.map(([a, b, w]) => ` { ${hex(a)}, ${hex(b)}, ${w} },`), "};", "/* Emoji_Presentation and Emoji_Modifier */", "typedef struct { uint32_t start, end; } ScrRlEmojiRange;", "static const ScrRlEmojiRange scr_rl_emoji_ranges[] = {", ...emoji.map(([a, b]) => ` { ${hex(a)}, ${hex(b)} },`), "};", "#endif", ]; const text = lines.join("\n") + "\n"; if (checkOnly) { if (readFileSync(out, "utf8") !== text) { console.error(`${out} is stale: run node scripts/gen-readline-width-table.mjs`); process.exit(1); } console.log(`${out} is current`); } else { await writeFile(out, text); console.log(`wrote ${out}`); }