[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
import test from "node:test";
import assert from "node:assert/strict";
import { stringCellWidth, sliceStringByCellColumns } from "./autocomplete/terminalStringCellWidth.ts";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
test("stringCellWidth counts ASCII as one cell each", () => {
assert.equal(stringCellWidth("docker"), 6);
});
test("stringCellWidth counts CJK ideographs as two cells each", () => {
assert.equal(stringCellWidth("部署"), 4);
});
test("sliceStringByCellColumns respects wide CJK cells", () => {
const prompt = String.raw`C:\Users\用户>`;
const line = `${prompt}部署`;
const promptCells = stringCellWidth(prompt);
assert.equal(sliceStringByCellColumns(line, 0, promptCells), prompt);
assert.equal(sliceStringByCellColumns(line, promptCells), "部署");
// Padding spaces after the content must not leak into the cursor prefix.
const padded = line + " ".repeat(20);
assert.equal(
sliceStringByCellColumns(padded, 0, stringCellWidth(line)),
line,
);
});
test("stringCellWidth collapses ZWJ emoji to one wide grapheme", () => {
assert.equal(stringCellWidth("👨‍💻"), 2);
});
test("stringCellWidth ignores combining marks inside a grapheme", () => {
// e + combining acute accent
assert.equal(stringCellWidth("e\u0301"), 1);
});
test("stringCellWidth matches xterm 15-graphemes for common emoji clusters", () => {
class El {
tagName: string;
style: Record<string, string> = {};
children: El[] = [];
classList = { add() {}, remove() {}, contains() { return false; } };
constructor(tag: string) { this.tagName = tag; }
appendChild(c: El) { this.children.push(c); return c; }
removeChild(c: El) { return c; }
addEventListener() {}
removeEventListener() {}
setAttribute() {}
getAttribute() { return null; }
getBoundingClientRect() { return { left: 0, top: 0, width: 0, height: 0 }; }
remove() {}
}
const previous = {
document: globalThis.document,
window: globalThis.window,
HTMLElement: globalThis.HTMLElement,
Element: globalThis.Element,
DocumentFragment: globalThis.DocumentFragment,
getComputedStyle: globalThis.getComputedStyle,
requestAnimationFrame: globalThis.requestAnimationFrame,
cancelAnimationFrame: globalThis.cancelAnimationFrame,
};
// Minimal DOM so @xterm/xterm can construct a Terminal in node tests.
(globalThis as { document?: unknown }).document = {
createElement: (t: string) => new El(t),
createDocumentFragment: () => new El("frag"),
addEventListener() {},
removeEventListener() {},
};
(globalThis as { window?: unknown }).window = globalThis;
(globalThis as { HTMLElement?: unknown }).HTMLElement = El;
(globalThis as { Element?: unknown }).Element = El;
(globalThis as { DocumentFragment?: unknown }).DocumentFragment = El;
(globalThis as { getComputedStyle?: unknown }).getComputedStyle = () => ({
getPropertyValue: () => "",
});
(globalThis as { requestAnimationFrame?: unknown }).requestAnimationFrame = (
cb: (t: number) => void,
) => setTimeout(() => cb(0), 0);
(globalThis as { cancelAnimationFrame?: unknown }).cancelAnimationFrame = (
id: number,
) => clearTimeout(id);
try {
const xterm = require("@xterm/xterm") as typeof import("@xterm/xterm");
const graphemes = require("@xterm/addon-unicode-graphemes") as typeof import("@xterm/addon-unicode-graphemes");
const term = new xterm.Terminal({ cols: 80, rows: 24, allowProposedApi: true });
term.loadAddon(new graphemes.UnicodeGraphemesAddon());
term.unicode.activeVersion = "15-graphemes";
const samples = ["🇨🇳", "1⃣", "©️", "🖥", "👨‍💻", "部署", "docker"];
for (const s of samples) {
const expected = (
term as unknown as {
_core: { unicodeService: { getStringCellWidth: (v: string) => number } };
}
)._core.unicodeService.getStringCellWidth(s);
assert.equal(
stringCellWidth(s, term),
expected,
`${JSON.stringify(s)} should match xterm width ${expected}`,
);
}
// These disagree with the hand-rolled fallback; the term path must win.
assert.equal(stringCellWidth("🇨🇳", term), 2);
assert.equal(stringCellWidth("1⃣", term), 2);
assert.equal(stringCellWidth("©️", term), 2);
assert.equal(stringCellWidth("🖥", term), 1);
} finally {
Object.assign(globalThis, previous);
}
});