Files
scriptc/scripts/gen-readline-width-table.mjs

83 lines
3.3 KiB
JavaScript

#!/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}`);
}