diff --git a/.zed/tasks.json b/.zed/tasks.json index 34420e6..4868825 100644 --- a/.zed/tasks.json +++ b/.zed/tasks.json @@ -1,10 +1,9 @@ +// Static tasks configuration. +// +// Example: [ { "label": "Test", "command": "deno test" - }, - { - "label": "Test - Watch", - "command": "deno test --watch" } ] diff --git a/deno.json b/deno.json index ffdf2b3..14c2394 100644 --- a/deno.json +++ b/deno.json @@ -1,9 +1,8 @@ { "name": "@textplace/core", - "version": "0.4.1", + "version": "0.3.0", "exports": "./mod.ts", "imports": { - "@deno/dnt": "jsr:@deno/dnt@^0.41.3", - "@std/cli": "jsr:@std/cli@^1.0.11" + "@deno/dnt": "jsr:@deno/dnt@^0.41.3" } } diff --git a/deno.lock b/deno.lock index afae4c4..491b9f0 100644 --- a/deno.lock +++ b/deno.lock @@ -7,7 +7,6 @@ "jsr:@std/assert@0.223": "0.223.0", "jsr:@std/assert@0.226": "0.226.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@1": "1.0.3", "jsr:@std/fs@0.223": "0.223.0", @@ -55,9 +54,6 @@ "@std/bytes@0.223.0": { "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" }, - "@std/cli@1.0.11": { - "integrity": "ec219619fdcd31bcf0d8e53bee1e2706ec9a02f70255365a094f69755dadd340" - }, "@std/fmt@0.223.0": { "integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208" }, @@ -155,8 +151,7 @@ }, "workspace": { "dependencies": [ - "jsr:@deno/dnt@~0.41.3", - "jsr:@std/cli@^1.0.11" + "jsr:@deno/dnt@~0.41.3" ] } } diff --git a/logic/board.ts b/logic/board.ts index 0436cdc..9dcb312 100644 --- a/logic/board.ts +++ b/logic/board.ts @@ -106,9 +106,9 @@ export function renderFullBoard(data: BoardData): FullBoard { return { w: lineLength, h: totalLineCount, - ch: chLines.flat(), - color: colorLines.flat(), - bg_color: bgColorLines.flat(), - width: widthLines.flat(), + ch: ([] as string[]).concat(...chLines).flat(), + color: ([] as string[]).concat(...colorLines).flat(), + bg_color: ([] as string[]).concat(...bgColorLines).flat(), + width: ([] as number[]).concat(...widthLines).flat(), }; } diff --git a/logic/character.ts b/logic/character.ts index 2d58dac..d1b9f69 100644 --- a/logic/character.ts +++ b/logic/character.ts @@ -1,6 +1,7 @@ -import { unicodeWidth } from "@std/cli/unicode-width"; - 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 { const segments = [...segmenter.segment(ch)]; @@ -10,6 +11,11 @@ export function getCharacterWidth(ch: string): number { ); } - // TODO: Properly fix this. - return Math.min(unicodeWidth(ch), 2); + const matchesASCII = ch.match(printableASCIIRegex); + const matchesCJK = ch.match(cjkRegex); + + if (!matchesASCII && !matchesCJK) throw new Error(`Invalid character: ${ch}`); + + // TODO: Support Emojis. + return matchesCJK ? 2 : 1; } diff --git a/logic/section.ts b/logic/section.ts index c3f976c..d935dc3 100644 --- a/logic/section.ts +++ b/logic/section.ts @@ -16,17 +16,17 @@ export function createSection( const offsetX = sx * boardConfig.sectionWidth; const offsetY = sy * boardConfig.sectionHeight; - const ch: string[][] = Array(boardConfig.sectionHeight).fill([]).map(() => - Array(boardConfig.sectionWidth).fill(boardConfig.defaultCh) + const ch: string[][] = Array(boardConfig.sectionHeight).fill( + Array(boardConfig.sectionWidth).fill(boardConfig.defaultCh), ); - const color: string[][] = Array(boardConfig.sectionHeight).fill([]).map(() => - Array(boardConfig.sectionWidth).fill(boardConfig.defaultColor) + const color: string[][] = Array(boardConfig.sectionHeight).fill( + Array(boardConfig.sectionWidth).fill(boardConfig.defaultColor), ); - const bgColor: string[][] = Array(boardConfig.sectionHeight).fill([]).map( - () => Array(boardConfig.sectionWidth).fill(boardConfig.defaultBgColor), + const bgColor: string[][] = Array(boardConfig.sectionHeight).fill( + Array(boardConfig.sectionWidth).fill(boardConfig.defaultBgColor), ); - const width: number[][] = Array(boardConfig.sectionHeight).fill([]).map(() => - Array(boardConfig.sectionWidth).fill(boardConfig.defaultWidth) + const width: number[][] = Array(boardConfig.sectionHeight).fill( + Array(boardConfig.sectionWidth).fill(boardConfig.defaultWidth), ); return { offsetX, offsetY, ch, color, bgColor, width }; @@ -44,7 +44,7 @@ export function applyChange(change: BoardChange, section: SectionData) { if (change.ch) { const chWidth = getCharacterWidth(change.ch); - const xCharacterOffset = xInSection % chWidth; + const xCharacterOffset = xInSection % 2; const offsetAdjustedXInSection = xInSection - xCharacterOffset; section.ch[yInSection][offsetAdjustedXInSection] = change.ch; section.width[yInSection][offsetAdjustedXInSection] = chWidth; diff --git a/tests/board.test.ts b/tests/board.test.ts index 735bf32..ecedc47 100644 --- a/tests/board.test.ts +++ b/tests/board.test.ts @@ -51,12 +51,11 @@ Deno.test("board", async (t) => { applyChangeOnBoard({ x: 4, y: 0, ch: "B" }, board); applyChangeOnBoard({ x: 0, y: 3, ch: "C" }, 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][1].ch[0][0], "B"); 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: 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][1].ch[2][0], "好"); assertEquals(board.sections[1][0].ch[1][0], "嗎"); - assertEquals(board.sections[1][1].ch[1], ["嘛", " ", " ", " "]); - - applyChangeOnBoard({ x: 5, y: 4, ch: "啊" }, board); - assertEquals(board.sections[1][1].ch[1], ["啊", " ", " ", " "]); + assertEquals(board.sections[1][1].ch[2][0], "嘛"); }); await t.step("getSectionOnBoard: existing section", () => { @@ -78,10 +74,10 @@ Deno.test("board", async (t) => { const section = getSectionOnBoard({ sx: 1, sy: 1 }, board, { readOnly: true, }); - assertEquals(section.ch[0], ["D", "E", " ", " "]); + assertEquals(section.ch[0][0], "嘛"); assertEquals(section.color[0][0], "F"); 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", () => { @@ -118,7 +114,7 @@ Deno.test("board", async (t) => { await t.step("on-demand creation: only changed sections are saved", () => { assert(board); - assertEquals(board.sections[2], undefined); - assertEquals(board.sections[0][2], undefined); + assertEquals(board.sections.length, 2); + assertEquals(board.sections[0].length, 2); }); }); diff --git a/tests/character.test.ts b/tests/character.test.ts index c8e3a65..886d7ad 100644 --- a/tests/character.test.ts +++ b/tests/character.test.ts @@ -26,18 +26,14 @@ Deno.test("getCharacterWidth CJK", () => { assertEquals(getCharacterWidth("グ"), 2); assertEquals(getCharacterWidth("ソ"), 2); - assertEquals(getCharacterWidth("?"), 2); - assertEquals(getCharacterWidth("!"), 2); + 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", () => { assertEquals(getCharacterWidth("𤲶"), 2); + + assertThrows(() => getCharacterWidth("𤲶"[0])); }); diff --git a/tests/checkFullBoard.ts b/tests/checkFullBoard.ts index 9484c40..9984249 100644 --- a/tests/checkFullBoard.ts +++ b/tests/checkFullBoard.ts @@ -47,11 +47,6 @@ export function checkFullBoard(board: FullBoard) { console.error("width: ", widthLine); }; - if (typeof cCh !== "string") { - printSituation(); - throw new Error("cCh is not string"); - } - if (!isValidColor(cCo) || !isValidColor(cBg)) { printSituation(); throw new Error("cCo or cBg is not valid"); diff --git a/tests/section.test.ts b/tests/section.test.ts index a70da5b..8a5a139 100644 --- a/tests/section.test.ts +++ b/tests/section.test.ts @@ -90,33 +90,27 @@ Deno.test("section", async (t) => { assert(section); applyChange({ x: 0, y: 0, ch: "t" }, section); - assertEquals(section.ch[0], ["t", " ", " ", " "]); - assertEquals(section.ch[1], [" ", " ", " ", " "]); - assertEquals(section.width[0], [1, 1, 1, 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]); + assertEquals(section.ch[0][0], "t"); + assertEquals(section.ch[0][1], " "); + assertEquals(section.width[0][0], 1); }); await t.step("applyChange 2-width at a correct position", () => { assert(section); applyChange({ x: 0, y: 0, ch: "あ" }, section); - assertEquals(section.ch[0], ["あ", "t", " ", " "]); - assertEquals(section.width[0], [2, 1, 1, 1]); + assertEquals(section.ch[0][0], "あ"); + assertEquals(section.ch[0][1], " "); + assertEquals(section.width[0][0], 2); }); await t.step("applyChange 2-width at an alternate position", () => { assert(section); applyChange({ x: 1, y: 0, ch: "あ" }, section); - assertEquals(section.ch[0], ["あ", "t", " ", " "]); - assertEquals(section.width[0], [2, 1, 1, 1]); + assertEquals(section.ch[0][0], "あ"); + assertEquals(section.ch[0][1], " "); + assertEquals(section.width[0][0], 2); }); await t.step("applyChange incorrect section", () => {