Compare commits

..

No commits in common. "main" and "0.3.0" have entirely different histories.
main ... 0.3.0

10 changed files with 48 additions and 68 deletions

View file

@ -1,10 +1,9 @@
// Static tasks configuration.
//
// Example:
[ [
{ {
"label": "Test", "label": "Test",
"command": "deno test" "command": "deno test"
},
{
"label": "Test - Watch",
"command": "deno test --watch"
} }
] ]

View file

@ -1,9 +1,8 @@
{ {
"name": "@textplace/core", "name": "@textplace/core",
"version": "0.4.1", "version": "0.3.0",
"exports": "./mod.ts", "exports": "./mod.ts",
"imports": { "imports": {
"@deno/dnt": "jsr:@deno/dnt@^0.41.3", "@deno/dnt": "jsr:@deno/dnt@^0.41.3"
"@std/cli": "jsr:@std/cli@^1.0.11"
} }
} }

7
deno.lock generated
View file

@ -7,7 +7,6 @@
"jsr:@std/assert@0.223": "0.223.0", "jsr:@std/assert@0.223": "0.223.0",
"jsr:@std/assert@0.226": "0.226.0", "jsr:@std/assert@0.226": "0.226.0",
"jsr:@std/bytes@0.223": "0.223.0", "jsr:@std/bytes@0.223": "0.223.0",
"jsr:@std/cli@^1.0.11": "1.0.11",
"jsr:@std/fmt@0.223": "0.223.0", "jsr:@std/fmt@0.223": "0.223.0",
"jsr:@std/fmt@1": "1.0.3", "jsr:@std/fmt@1": "1.0.3",
"jsr:@std/fs@0.223": "0.223.0", "jsr:@std/fs@0.223": "0.223.0",
@ -55,9 +54,6 @@
"@std/bytes@0.223.0": { "@std/bytes@0.223.0": {
"integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8"
}, },
"@std/cli@1.0.11": {
"integrity": "ec219619fdcd31bcf0d8e53bee1e2706ec9a02f70255365a094f69755dadd340"
},
"@std/fmt@0.223.0": { "@std/fmt@0.223.0": {
"integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208" "integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208"
}, },
@ -155,8 +151,7 @@
}, },
"workspace": { "workspace": {
"dependencies": [ "dependencies": [
"jsr:@deno/dnt@~0.41.3", "jsr:@deno/dnt@~0.41.3"
"jsr:@std/cli@^1.0.11"
] ]
} }
} }

View file

@ -106,9 +106,9 @@ export function renderFullBoard(data: BoardData): FullBoard {
return { return {
w: lineLength, w: lineLength,
h: totalLineCount, h: totalLineCount,
ch: chLines.flat(), ch: ([] as string[]).concat(...chLines).flat(),
color: colorLines.flat(), color: ([] as string[]).concat(...colorLines).flat(),
bg_color: bgColorLines.flat(), bg_color: ([] as string[]).concat(...bgColorLines).flat(),
width: widthLines.flat(), width: ([] as number[]).concat(...widthLines).flat(),
}; };
} }

View file

@ -1,6 +1,7 @@
import { unicodeWidth } from "@std/cli/unicode-width";
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" }); const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
const cjkRegex =
/[\p{Unified_Ideograph}\u30A0-\u30FF\u3040-\u309F\u31F0-\u31FF]/u;
const printableASCIIRegex = /^[\x20-\x7E]$/;
export function getCharacterWidth(ch: string): number { export function getCharacterWidth(ch: string): number {
const segments = [...segmenter.segment(ch)]; const segments = [...segmenter.segment(ch)];
@ -10,6 +11,11 @@ export function getCharacterWidth(ch: string): number {
); );
} }
// TODO: Properly fix this. const matchesASCII = ch.match(printableASCIIRegex);
return Math.min(unicodeWidth(ch), 2); const matchesCJK = ch.match(cjkRegex);
if (!matchesASCII && !matchesCJK) throw new Error(`Invalid character: ${ch}`);
// TODO: Support Emojis.
return matchesCJK ? 2 : 1;
} }

View file

@ -16,17 +16,17 @@ export function createSection(
const offsetX = sx * boardConfig.sectionWidth; const offsetX = sx * boardConfig.sectionWidth;
const offsetY = sy * boardConfig.sectionHeight; const offsetY = sy * boardConfig.sectionHeight;
const ch: string[][] = Array(boardConfig.sectionHeight).fill([]).map(() => const ch: string[][] = Array(boardConfig.sectionHeight).fill(
Array(boardConfig.sectionWidth).fill(boardConfig.defaultCh) Array(boardConfig.sectionWidth).fill(boardConfig.defaultCh),
); );
const color: string[][] = Array(boardConfig.sectionHeight).fill([]).map(() => const color: string[][] = Array(boardConfig.sectionHeight).fill(
Array(boardConfig.sectionWidth).fill(boardConfig.defaultColor) Array(boardConfig.sectionWidth).fill(boardConfig.defaultColor),
); );
const bgColor: string[][] = Array(boardConfig.sectionHeight).fill([]).map( const bgColor: string[][] = Array(boardConfig.sectionHeight).fill(
() => Array(boardConfig.sectionWidth).fill(boardConfig.defaultBgColor), Array(boardConfig.sectionWidth).fill(boardConfig.defaultBgColor),
); );
const width: number[][] = Array(boardConfig.sectionHeight).fill([]).map(() => const width: number[][] = Array(boardConfig.sectionHeight).fill(
Array(boardConfig.sectionWidth).fill(boardConfig.defaultWidth) Array(boardConfig.sectionWidth).fill(boardConfig.defaultWidth),
); );
return { offsetX, offsetY, ch, color, bgColor, width }; return { offsetX, offsetY, ch, color, bgColor, width };
@ -44,7 +44,7 @@ export function applyChange(change: BoardChange, section: SectionData) {
if (change.ch) { if (change.ch) {
const chWidth = getCharacterWidth(change.ch); const chWidth = getCharacterWidth(change.ch);
const xCharacterOffset = xInSection % chWidth; const xCharacterOffset = xInSection % 2;
const offsetAdjustedXInSection = xInSection - xCharacterOffset; const offsetAdjustedXInSection = xInSection - xCharacterOffset;
section.ch[yInSection][offsetAdjustedXInSection] = change.ch; section.ch[yInSection][offsetAdjustedXInSection] = change.ch;
section.width[yInSection][offsetAdjustedXInSection] = chWidth; section.width[yInSection][offsetAdjustedXInSection] = chWidth;

View file

@ -51,12 +51,11 @@ Deno.test("board", async (t) => {
applyChangeOnBoard({ x: 4, y: 0, ch: "B" }, board); applyChangeOnBoard({ x: 4, y: 0, ch: "B" }, board);
applyChangeOnBoard({ x: 0, y: 3, ch: "C" }, board); applyChangeOnBoard({ x: 0, y: 3, ch: "C" }, board);
applyChangeOnBoard({ x: 4, y: 3, ch: "D" }, board); applyChangeOnBoard({ x: 4, y: 3, ch: "D" }, board);
applyChangeOnBoard({ x: 5, y: 3, ch: "E" }, board);
assertEquals(board.sections[0][0].ch[0][0], "A"); assertEquals(board.sections[0][0].ch[0][0], "A");
assertEquals(board.sections[0][1].ch[0][0], "B"); assertEquals(board.sections[0][1].ch[0][0], "B");
assertEquals(board.sections[1][0].ch[0][0], "C"); assertEquals(board.sections[1][0].ch[0][0], "C");
assertEquals(board.sections[1][1].ch[0], ["D", "E", " ", " "]); assertEquals(board.sections[1][1].ch[0][0], "D");
applyChangeOnBoard({ x: 0, y: 1, ch: "你" }, board); applyChangeOnBoard({ x: 0, y: 1, ch: "你" }, board);
applyChangeOnBoard({ x: 4, y: 2, ch: "好" }, board); applyChangeOnBoard({ x: 4, y: 2, ch: "好" }, board);
@ -66,10 +65,7 @@ Deno.test("board", async (t) => {
assertEquals(board.sections[0][0].ch[1][0], "你"); assertEquals(board.sections[0][0].ch[1][0], "你");
assertEquals(board.sections[0][1].ch[2][0], "好"); assertEquals(board.sections[0][1].ch[2][0], "好");
assertEquals(board.sections[1][0].ch[1][0], "嗎"); assertEquals(board.sections[1][0].ch[1][0], "嗎");
assertEquals(board.sections[1][1].ch[1], ["嘛", " ", " ", " "]); assertEquals(board.sections[1][1].ch[2][0], "嘛");
applyChangeOnBoard({ x: 5, y: 4, ch: "啊" }, board);
assertEquals(board.sections[1][1].ch[1], ["啊", " ", " ", " "]);
}); });
await t.step("getSectionOnBoard: existing section", () => { await t.step("getSectionOnBoard: existing section", () => {
@ -78,10 +74,10 @@ Deno.test("board", async (t) => {
const section = getSectionOnBoard({ sx: 1, sy: 1 }, board, { const section = getSectionOnBoard({ sx: 1, sy: 1 }, board, {
readOnly: true, readOnly: true,
}); });
assertEquals(section.ch[0], ["D", "E", " ", " "]); assertEquals(section.ch[0][0], "嘛");
assertEquals(section.color[0][0], "F"); assertEquals(section.color[0][0], "F");
assertEquals(section.bgColor[0][0], "0"); assertEquals(section.bgColor[0][0], "0");
assertEquals(section.width[0], [1, 1, 1, 1]); assertEquals(section.width[0][0], 2);
}); });
await t.step("getSectionOnBoard: non-existing row", () => { await t.step("getSectionOnBoard: non-existing row", () => {
@ -118,7 +114,7 @@ Deno.test("board", async (t) => {
await t.step("on-demand creation: only changed sections are saved", () => { await t.step("on-demand creation: only changed sections are saved", () => {
assert(board); assert(board);
assertEquals(board.sections[2], undefined); assertEquals(board.sections.length, 2);
assertEquals(board.sections[0][2], undefined); assertEquals(board.sections[0].length, 2);
}); });
}); });

View file

@ -26,18 +26,14 @@ Deno.test("getCharacterWidth CJK", () => {
assertEquals(getCharacterWidth("グ"), 2); assertEquals(getCharacterWidth("グ"), 2);
assertEquals(getCharacterWidth("ソ"), 2); assertEquals(getCharacterWidth("ソ"), 2);
assertEquals(getCharacterWidth(""), 2); assertThrows(() => getCharacterWidth(""));
assertEquals(getCharacterWidth(""), 2); assertThrows(() => getCharacterWidth(""));
assertThrows(() => getCharacterWidth("你好")); assertThrows(() => getCharacterWidth("你好"));
assertThrows(() => getCharacterWidth("ヨスガノ")); assertThrows(() => getCharacterWidth("ヨスガノ"));
}); });
Deno.test("getCharacterWidth Emoji", () => {
assertEquals(getCharacterWidth("👋"), 2);
assertEquals(getCharacterWidth("🌲️"), 2);
assertEquals(getCharacterWidth("👨‍👩‍👧‍👦"), 2);
});
Deno.test("getCharacterWidth previously faulty cases", () => { Deno.test("getCharacterWidth previously faulty cases", () => {
assertEquals(getCharacterWidth("𤲶"), 2); assertEquals(getCharacterWidth("𤲶"), 2);
assertThrows(() => getCharacterWidth("𤲶"[0]));
}); });

View file

@ -47,11 +47,6 @@ export function checkFullBoard(board: FullBoard) {
console.error("width: ", widthLine); console.error("width: ", widthLine);
}; };
if (typeof cCh !== "string") {
printSituation();
throw new Error("cCh is not string");
}
if (!isValidColor(cCo) || !isValidColor(cBg)) { if (!isValidColor(cCo) || !isValidColor(cBg)) {
printSituation(); printSituation();
throw new Error("cCo or cBg is not valid"); throw new Error("cCo or cBg is not valid");

View file

@ -90,33 +90,27 @@ Deno.test("section", async (t) => {
assert(section); assert(section);
applyChange({ x: 0, y: 0, ch: "t" }, section); applyChange({ x: 0, y: 0, ch: "t" }, section);
assertEquals(section.ch[0], ["t", " ", " ", " "]); assertEquals(section.ch[0][0], "t");
assertEquals(section.ch[1], [" ", " ", " ", " "]); assertEquals(section.ch[0][1], " ");
assertEquals(section.width[0], [1, 1, 1, 1]); assertEquals(section.width[0][0], 1);
});
await t.step("applyChange 1-width at odd position", () => {
assert(section);
applyChange({ x: 1, y: 0, ch: "t" }, section);
assertEquals(section.ch[0], ["t", "t", " ", " "]);
assertEquals(section.width[0], [1, 1, 1, 1]);
}); });
await t.step("applyChange 2-width at a correct position", () => { await t.step("applyChange 2-width at a correct position", () => {
assert(section); assert(section);
applyChange({ x: 0, y: 0, ch: "あ" }, section); applyChange({ x: 0, y: 0, ch: "あ" }, section);
assertEquals(section.ch[0], ["あ", "t", " ", " "]); assertEquals(section.ch[0][0], "あ");
assertEquals(section.width[0], [2, 1, 1, 1]); assertEquals(section.ch[0][1], " ");
assertEquals(section.width[0][0], 2);
}); });
await t.step("applyChange 2-width at an alternate position", () => { await t.step("applyChange 2-width at an alternate position", () => {
assert(section); assert(section);
applyChange({ x: 1, y: 0, ch: "あ" }, section); applyChange({ x: 1, y: 0, ch: "あ" }, section);
assertEquals(section.ch[0], ["あ", "t", " ", " "]); assertEquals(section.ch[0][0], "あ");
assertEquals(section.width[0], [2, 1, 1, 1]); assertEquals(section.ch[0][1], " ");
assertEquals(section.width[0][0], 2);
}); });
await t.step("applyChange incorrect section", () => { await t.step("applyChange incorrect section", () => {