diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml index f0998d8..374fe32 100644 --- a/.github/workflows/deno.yml +++ b/.github/workflows/deno.yml @@ -26,9 +26,10 @@ jobs: uses: actions/checkout@v4 - name: Setup Deno - uses: denoland/setup-deno@v2 + # uses: denoland/setup-deno@v1 + uses: denoland/setup-deno@61fe2df320078202e33d7d5ad347e7dcfa0e8f31 # v1.1.2 with: - deno-version: v2.x + deno-version: v1.x # Uncomment this step to verify the use of 'deno fmt' on each commit. - name: Verify formatting diff --git a/.github/workflows/publish_npm.yml b/.github/workflows/publish_npm.yml index 6d8c23b..eecb58c 100644 --- a/.github/workflows/publish_npm.yml +++ b/.github/workflows/publish_npm.yml @@ -9,17 +9,18 @@ jobs: contents: read id-token: write steps: - - uses: actions/checkout@v4 - - name: Setup Deno - uses: denoland/setup-deno@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: - deno-version: v2.x - # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v4 - with: - node-version: "22.x" + node-version: "18.x" registry-url: "https://registry.npmjs.org" + - name: Setup Deno + # uses: denoland/setup-deno@v1 + uses: denoland/setup-deno@61fe2df320078202e33d7d5ad347e7dcfa0e8f31 # v1.1.2 + with: + deno-version: v1.x - run: deno run -A scripts/build_npm.ts + - run: npm install -g npm - run: cd npm && npm ci - run: cd npm && npm publish --provenance --access public env: 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..e87716f 100644 --- a/deno.json +++ b/deno.json @@ -1,9 +1,8 @@ { "name": "@textplace/core", - "version": "0.4.1", + "version": "0.1.3", "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.1" } } diff --git a/deno.lock b/deno.lock index afae4c4..cb732d5 100644 --- a/deno.lock +++ b/deno.lock @@ -1,121 +1,216 @@ { - "version": "4", - "specifiers": { - "jsr:@david/code-block-writer@^13.0.2": "13.0.3", - "jsr:@deno/cache-dir@~0.10.3": "0.10.3", - "jsr:@deno/dnt@~0.41.3": "0.41.3", - "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", - "jsr:@std/fs@1": "1.0.6", - "jsr:@std/fs@~0.229.3": "0.229.3", - "jsr:@std/io@0.223": "0.223.0", - "jsr:@std/path@0.223": "0.223.0", - "jsr:@std/path@1": "1.0.8", - "jsr:@std/path@1.0.0-rc.1": "1.0.0-rc.1", - "jsr:@std/path@^1.0.8": "1.0.8", - "jsr:@std/path@~0.225.2": "0.225.2", - "jsr:@ts-morph/bootstrap@0.24": "0.24.0", - "jsr:@ts-morph/common@0.24": "0.24.0" - }, - "jsr": { - "@david/code-block-writer@13.0.3": { - "integrity": "f98c77d320f5957899a61bfb7a9bead7c6d83ad1515daee92dbacc861e13bb7f" + "version": "3", + "packages": { + "specifiers": { + "jsr:@deno/cache-dir@^0.8.0": "jsr:@deno/cache-dir@0.8.0", + "jsr:@deno/dnt@^0.41.1": "jsr:@deno/dnt@0.41.1", + "jsr:@deno/graph@^0.69.7": "jsr:@deno/graph@0.69.10", + "jsr:@std/assert@^0.218.2": "jsr:@std/assert@0.218.2", + "jsr:@std/bytes@^0.218.2": "jsr:@std/bytes@0.218.2", + "jsr:@std/fmt@^0.218.2": "jsr:@std/fmt@0.218.2", + "jsr:@std/fs@^0.218.2": "jsr:@std/fs@0.218.2", + "jsr:@std/io@^0.218.2": "jsr:@std/io@0.218.2", + "jsr:@std/path@^0.218.2": "jsr:@std/path@0.218.2", + "npm:@ts-morph/bootstrap@0.22": "npm:@ts-morph/bootstrap@0.22.0", + "npm:code-block-writer@^13.0.1": "npm:code-block-writer@13.0.1" }, - "@deno/cache-dir@0.10.3": { - "integrity": "eb022f84ecc49c91d9d98131c6e6b118ff63a29e343624d058646b9d50404776", - "dependencies": [ - "jsr:@std/fmt@0.223", - "jsr:@std/fs@0.223", - "jsr:@std/io", - "jsr:@std/path@0.223" - ] + "jsr": { + "@deno/cache-dir@0.8.0": { + "integrity": "e87e80a404958f6350d903e6238b72afb92468378b0b32111f7a1e4916ac7fe7", + "dependencies": [ + "jsr:@deno/graph@^0.69.7", + "jsr:@std/fs@^0.218.2", + "jsr:@std/io@^0.218.2" + ] + }, + "@deno/dnt@0.41.1": { + "integrity": "8746a773e031ae19ef43d0eece850217b76cf1d0118fdd8e059652d7023d4aff", + "dependencies": [ + "jsr:@deno/cache-dir@^0.8.0", + "jsr:@std/fmt@^0.218.2", + "jsr:@std/fs@^0.218.2", + "jsr:@std/path@^0.218.2", + "npm:@ts-morph/bootstrap@0.22", + "npm:code-block-writer@^13.0.1" + ] + }, + "@deno/graph@0.69.10": { + "integrity": "38fe22ac5686f6ece5daeec5a4df65c6314d7d32adcc33f77917a13cfaffa26f" + }, + "@std/assert@0.218.2": { + "integrity": "7f0a5a1a8cf86607cd6c2c030584096e1ffad27fc9271429a8cb48cfbdee5eaf" + }, + "@std/bytes@0.218.2": { + "integrity": "91fe54b232dcca73856b79a817247f4a651dbb60d51baafafb6408c137241670" + }, + "@std/fmt@0.218.2": { + "integrity": "99526449d2505aa758b6cbef81e7dd471d8b28ec0dcb1491d122b284c548788a" + }, + "@std/fs@0.218.2": { + "integrity": "dd9431453f7282e8c577cc22c9e6d036055a9a980b5549f887d6012969fabcca", + "dependencies": [ + "jsr:@std/assert@^0.218.2", + "jsr:@std/path@^0.218.2" + ] + }, + "@std/io@0.218.2": { + "integrity": "c64fbfa087b7c9d4d386c5672f291f607d88cb7d44fc299c20c713e345f2785f", + "dependencies": [ + "jsr:@std/bytes@^0.218.2" + ] + }, + "@std/path@0.218.2": { + "integrity": "b568fd923d9e53ad76d17c513e7310bda8e755a3e825e6289a0ce536404e2662", + "dependencies": [ + "jsr:@std/assert@^0.218.2" + ] + } }, - "@deno/dnt@0.41.3": { - "integrity": "b2ef2c8a5111eef86cb5bfcae103d6a2938e8e649e2461634a7befb7fc59d6d2", - "dependencies": [ - "jsr:@david/code-block-writer", - "jsr:@deno/cache-dir", - "jsr:@std/fmt@1", - "jsr:@std/fs@1", - "jsr:@std/path@1", - "jsr:@ts-morph/bootstrap" - ] - }, - "@std/assert@0.223.0": { - "integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24" - }, - "@std/assert@0.226.0": { - "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3" - }, - "@std/bytes@0.223.0": { - "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" - }, - "@std/cli@1.0.11": { - "integrity": "ec219619fdcd31bcf0d8e53bee1e2706ec9a02f70255365a094f69755dadd340" - }, - "@std/fmt@0.223.0": { - "integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208" - }, - "@std/fmt@1.0.3": { - "integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f" - }, - "@std/fs@0.223.0": { - "integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c" - }, - "@std/fs@0.229.3": { - "integrity": "783bca21f24da92e04c3893c9e79653227ab016c48e96b3078377ebd5222e6eb", - "dependencies": [ - "jsr:@std/path@1.0.0-rc.1" - ] - }, - "@std/fs@1.0.6": { - "integrity": "42b56e1e41b75583a21d5a37f6a6a27de9f510bcd36c0c85791d685ca0b85fa2", - "dependencies": [ - "jsr:@std/path@^1.0.8" - ] - }, - "@std/io@0.223.0": { - "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", - "dependencies": [ - "jsr:@std/assert@0.223", - "jsr:@std/bytes" - ] - }, - "@std/path@0.223.0": { - "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989", - "dependencies": [ - "jsr:@std/assert@0.223" - ] - }, - "@std/path@0.225.2": { - "integrity": "0f2db41d36b50ef048dcb0399aac720a5348638dd3cb5bf80685bf2a745aa506", - "dependencies": [ - "jsr:@std/assert@0.226" - ] - }, - "@std/path@1.0.0-rc.1": { - "integrity": "b8c00ae2f19106a6bb7cbf1ab9be52aa70de1605daeb2dbdc4f87a7cbaf10ff6" - }, - "@std/path@1.0.8": { - "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" - }, - "@ts-morph/bootstrap@0.24.0": { - "integrity": "a826a2ef7fa8a7c3f1042df2c034d20744d94da2ee32bf29275bcd4dffd3c060", - "dependencies": [ - "jsr:@ts-morph/common" - ] - }, - "@ts-morph/common@0.24.0": { - "integrity": "12b625b8e562446ba658cdbe9ad77774b4bd96b992ae8bd34c60dbf24d06c1f3", - "dependencies": [ - "jsr:@std/fs@~0.229.3", - "jsr:@std/path@~0.225.2" - ] + "npm": { + "@nodelib/fs.scandir@2.1.5": { + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "@nodelib/fs.stat@2.0.5", + "run-parallel": "run-parallel@1.2.0" + } + }, + "@nodelib/fs.stat@2.0.5": { + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dependencies": {} + }, + "@nodelib/fs.walk@1.2.8": { + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "@nodelib/fs.scandir@2.1.5", + "fastq": "fastq@1.17.1" + } + }, + "@ts-morph/bootstrap@0.22.0": { + "integrity": "sha512-MI5q7pid4swAlE2lcHwHRa6rcjoIMyT6fy8uuZm8BGg7DHGi/H5bQ0GMZzbk3N0r/LfStMdOYPkl+3IwvfIQ2g==", + "dependencies": { + "@ts-morph/common": "@ts-morph/common@0.22.0" + } + }, + "@ts-morph/common@0.22.0": { + "integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==", + "dependencies": { + "fast-glob": "fast-glob@3.3.2", + "minimatch": "minimatch@9.0.4", + "mkdirp": "mkdirp@3.0.1", + "path-browserify": "path-browserify@1.0.1" + } + }, + "balanced-match@1.0.2": { + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dependencies": {} + }, + "brace-expansion@2.0.1": { + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "balanced-match@1.0.2" + } + }, + "braces@3.0.2": { + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "fill-range@7.0.1" + } + }, + "code-block-writer@13.0.1": { + "integrity": "sha512-c5or4P6erEA69TxaxTNcHUNcIn+oyxSRTOWV+pSYF+z4epXqNvwvJ70XPGjPNgue83oAFAPBRQYwpAJ/Hpe/Sg==", + "dependencies": {} + }, + "fast-glob@3.3.2": { + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": { + "@nodelib/fs.stat": "@nodelib/fs.stat@2.0.5", + "@nodelib/fs.walk": "@nodelib/fs.walk@1.2.8", + "glob-parent": "glob-parent@5.1.2", + "merge2": "merge2@1.4.1", + "micromatch": "micromatch@4.0.5" + } + }, + "fastq@1.17.1": { + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": { + "reusify": "reusify@1.0.4" + } + }, + "fill-range@7.0.1": { + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "to-regex-range@5.0.1" + } + }, + "glob-parent@5.1.2": { + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "is-glob@4.0.3" + } + }, + "is-extglob@2.1.1": { + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dependencies": {} + }, + "is-glob@4.0.3": { + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "is-extglob@2.1.1" + } + }, + "is-number@7.0.0": { + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dependencies": {} + }, + "merge2@1.4.1": { + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dependencies": {} + }, + "micromatch@4.0.5": { + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "braces@3.0.2", + "picomatch": "picomatch@2.3.1" + } + }, + "minimatch@9.0.4": { + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dependencies": { + "brace-expansion": "brace-expansion@2.0.1" + } + }, + "mkdirp@3.0.1": { + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dependencies": {} + }, + "path-browserify@1.0.1": { + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dependencies": {} + }, + "picomatch@2.3.1": { + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dependencies": {} + }, + "queue-microtask@1.2.3": { + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dependencies": {} + }, + "reusify@1.0.4": { + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dependencies": {} + }, + "run-parallel@1.2.0": { + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dependencies": { + "queue-microtask": "queue-microtask@1.2.3" + } + }, + "to-regex-range@5.0.1": { + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "is-number@7.0.0" + } + } } }, "remote": { @@ -155,8 +250,7 @@ }, "workspace": { "dependencies": [ - "jsr:@deno/dnt@~0.41.3", - "jsr:@std/cli@^1.0.11" + "jsr:@deno/dnt@^0.41.1" ] } } diff --git a/logic/board.ts b/logic/board.ts index 0436cdc..3503de7 100644 --- a/logic/board.ts +++ b/logic/board.ts @@ -9,7 +9,15 @@ import { applyChange, createSection } from "./section.ts"; import type { BoardChange } from "../types/change.ts"; export function createBoard(config: BoardConfig): BoardData { - return { config, sections: [] }; + const sections: SectionData[][] = Array(config.ySections) + .fill(0) + .map((_, sy) => + Array(config.xSections) + .fill(0) + .map((_, sx) => createSection({ sx, sy }, config)) + ); + + return { config, sections }; } export function locateSection( @@ -22,38 +30,9 @@ export function locateSection( }; } -/** - * Get a section from board. - * - * If the section does not exist yet, it will be created and (optionally) added to `board`. - */ -export function getSectionOnBoard( - { sx, sy }: SectionPosition, - board: BoardData, - options: { - /** - * Whether the section data is only used for reading. - * - * If `true`, this function will return an empty section configured with default values, but will not add it to the board data to save storage. - */ - readOnly: boolean; - } = { readOnly: false }, -): SectionData { - let section: SectionData; - if (!board.sections[sy] && !options.readOnly) board.sections[sy] = []; - - if (!board.sections[sy]?.[sx]) { - section = createSection({ sx, sy }, board.config); - if (!options.readOnly) board.sections[sy][sx] = section; - } else { - section = board.sections[sy][sx]; - } - return section; -} - export function applyChangeOnBoard(change: BoardChange, board: BoardData) { - const sPos = locateSection(change, board.config); - const section = getSectionOnBoard(sPos, board); + const { sx, sy } = locateSection(change, board.config); + const section = board.sections[sy][sx]; applyChange(change, section); } @@ -61,16 +40,16 @@ export function renderFullBoard(data: BoardData): FullBoard { const totalLineCount = data.config.sectionHeight * data.config.ySections; const lineLength = data.config.sectionWidth * data.config.xSections; - const chLines: string[][] = Array(totalLineCount); - const colorLines: string[][] = Array(totalLineCount); - const bgColorLines: string[][] = Array(totalLineCount); - const widthLines: number[][] = Array(totalLineCount); + const chLines: string[] = Array(totalLineCount); + const colorLines: string[] = Array(totalLineCount); + const bgColorLines: string[] = Array(totalLineCount); + const widthLines: string[] = Array(totalLineCount); for (let y = 0; y < totalLineCount; y++) { - const chLine: string[] = []; - const colorLine: string[] = []; - const bgColorLine: string[] = []; - const widthLine: number[] = []; + let chLine = ""; + let colorLine = ""; + let bgColorLine = ""; + let widthLine = ""; let charsToSkip = 0; @@ -80,8 +59,8 @@ export function renderFullBoard(data: BoardData): FullBoard { continue; } - const sPos = locateSection({ x, y }, data.config); - const section = getSectionOnBoard(sPos, data, { readOnly: true }); + const { sx, sy } = locateSection({ x, y }, data.config); + const section = data.sections[sy][sx]; const xInSection = x % data.config.sectionWidth; const yInSection = y % data.config.sectionHeight; @@ -90,10 +69,10 @@ export function renderFullBoard(data: BoardData): FullBoard { const cBg = section.bgColor[yInSection][xInSection]; const cWd = section.width[yInSection][xInSection]; - chLine.push(cCh); - colorLine.push(cCo); - bgColorLine.push(cBg); - widthLine.push(cWd); + chLine += cCh; + colorLine += cCo; + bgColorLine += cBg; + widthLine += cWd.toString(); charsToSkip += cWd - 1; } @@ -106,9 +85,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: chLines.join("\n"), + color: colorLines.join("\n"), + bg_color: bgColorLines.join("\n"), + width: widthLines.join("\n"), }; } 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/scripts/build_npm.ts b/scripts/build_npm.ts index bcf6721..dc61da3 100644 --- a/scripts/build_npm.ts +++ b/scripts/build_npm.ts @@ -22,10 +22,10 @@ await build({ license: "MIT", repository: { type: "git", - url: "https://github.com/TextPlace/CoreTextPlace", + url: "git+https://github.com/textplace/CoreTextPlace.git", }, bugs: { - url: "https://github.com/TextPlace/CoreTextPlace/issues", + url: "https://github.com/textplace/CoreTextPlace/issues", }, }, postBuild() { diff --git a/tests/board.test.ts b/tests/board.test.ts index 735bf32..29cd7cd 100644 --- a/tests/board.test.ts +++ b/tests/board.test.ts @@ -3,11 +3,7 @@ import { assertEquals, } from "https://deno.land/std@0.224.0/assert/mod.ts"; -import { - createBoard, - getSectionOnBoard, - renderFullBoard, -} from "../logic/board.ts"; +import { createBoard, renderFullBoard } from "../logic/board.ts"; import type { BoardData } from "../types/board.ts"; import { checkFullBoard } from "./checkFullBoard.ts"; import { locateSection } from "../logic/board.ts"; @@ -18,8 +14,8 @@ Deno.test("board", async (t) => { await t.step("createBoard", () => { board = createBoard({ - xSections: 3, - ySections: 3, + xSections: 2, + ySections: 2, sectionWidth: 4, sectionHeight: 3, defaultCh: " ", @@ -28,8 +24,17 @@ Deno.test("board", async (t) => { defaultWidth: 1, }); - // Sections are created on demand. - assertEquals(board.sections.length, 0); + assertEquals(board.sections.length, 2); + assertEquals(board.sections[0].length, 2); + assertEquals(board.sections[0][0].offsetX, 0); + assertEquals(board.sections[0][0].offsetY, 0); + assertEquals(board.sections[0][1].offsetX, 4); + assertEquals(board.sections[0][1].offsetY, 0); + assertEquals(board.sections[1].length, 2); + assertEquals(board.sections[1][0].offsetX, 0); + assertEquals(board.sections[1][0].offsetY, 3); + assertEquals(board.sections[1][1].offsetX, 4); + assertEquals(board.sections[1][1].offsetY, 3); }); await t.step("locateSection", () => { @@ -51,12 +56,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,46 +70,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], ["啊", " ", " ", " "]); - }); - - await t.step("getSectionOnBoard: existing section", () => { - assert(board); - - const section = getSectionOnBoard({ sx: 1, sy: 1 }, board, { - readOnly: true, - }); - assertEquals(section.ch[0], ["D", "E", " ", " "]); - assertEquals(section.color[0][0], "F"); - assertEquals(section.bgColor[0][0], "0"); - assertEquals(section.width[0], [1, 1, 1, 1]); - }); - - await t.step("getSectionOnBoard: non-existing row", () => { - assert(board); - - const section = getSectionOnBoard({ sx: 1, sy: 2 }, board, { - readOnly: true, - }); - assertEquals(section.ch[0][0], " "); - assertEquals(section.color[0][0], "F"); - assertEquals(section.bgColor[0][0], "0"); - assertEquals(section.width[0][0], 1); - }); - - await t.step("getSectionOnBoard: non-existing section", () => { - assert(board); - - const section = getSectionOnBoard({ sx: 2, sy: 1 }, board, { - readOnly: true, - }); - assertEquals(section.ch[0][0], " "); - assertEquals(section.color[0][0], "F"); - assertEquals(section.bgColor[0][0], "0"); - assertEquals(section.width[0][0], 1); + assertEquals(board.sections[1][1].ch[2][0], "嘛"); }); await t.step("renderFullBoard", () => { @@ -114,11 +79,4 @@ Deno.test("board", async (t) => { const rendered = renderFullBoard(board); checkFullBoard(rendered); }); - - await t.step("on-demand creation: only changed sections are saved", () => { - assert(board); - - assertEquals(board.sections[2], undefined); - assertEquals(board.sections[0][2], undefined); - }); }); 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..78d916c 100644 --- a/tests/checkFullBoard.ts +++ b/tests/checkFullBoard.ts @@ -1,8 +1,8 @@ import { getCharacterWidth } from "../logic/character.ts"; import type { FullBoard } from "../types/board.ts"; -function isCorrectWidth(cWd: number, cCh: string): boolean { - return getCharacterWidth(cCh) === cWd; +function isCorrectWidth(cWdRaw: string, cCh: string): boolean { + return getCharacterWidth(cCh).toString() === cWdRaw; } function isValidColor(color: string): boolean { @@ -24,7 +24,8 @@ export function checkFullBoard(board: FullBoard) { const cCh = ch[i]; const cCo = board.color[i]; const cBg = board.bg_color[i]; - const cWd = board.width[i]; + const cWdRaw = board.width[i]; + const cWd = parseInt(cWdRaw); const printSituation = () => { console.error( @@ -39,7 +40,7 @@ export function checkFullBoard(board: FullBoard) { "cBg:", JSON.stringify(cBg), "cWd:", - JSON.stringify(cWd), + JSON.stringify(cWdRaw), ); console.error("ch: ", chLine); console.error("color: ", colorLine); @@ -47,9 +48,33 @@ export function checkFullBoard(board: FullBoard) { console.error("width: ", widthLine); }; - if (typeof cCh !== "string") { - printSituation(); - throw new Error("cCh is not string"); + if (cCh === "\n") { + if (cCo !== "\n" || cBg !== "\n" || cWdRaw !== "\n") { + printSituation(); + throw new Error("cCh is newline while at least one other field aren't"); + } + + if (colorLine.length !== board.w) { + printSituation(); + throw new Error("color line length error"); + } + if (bgColorLine.length !== board.w) { + printSituation(); + throw new Error("bg color line length error"); + } + if (widthLine.length !== board.w) { + printSituation(); + throw new Error("width line length error"); + } + + chLine = ""; + colorLine = ""; + bgColorLine = ""; + widthLine = ""; + lines++; + unsafeCurrentOffset += cCh.length; + + continue; } if (!isValidColor(cCo) || !isValidColor(cBg)) { @@ -57,7 +82,7 @@ export function checkFullBoard(board: FullBoard) { throw new Error("cCo or cBg is not valid"); } - if (!isCorrectWidth(cWd, cCh)) { + if (!isCorrectWidth(cWdRaw, cCh)) { printSituation(); throw new Error("cWd is wrong"); } @@ -65,17 +90,9 @@ export function checkFullBoard(board: FullBoard) { chLine += cCh; colorLine += cCo.padEnd(cWd); bgColorLine += cBg.padEnd(cWd); - widthLine += String(cWd).padEnd(cWd); + widthLine += cWdRaw.padEnd(cWd); unsafeCurrentOffset += cCh.length; - - if (colorLine.length === board.w) { - lines++; - chLine = ""; - colorLine = ""; - bgColorLine = ""; - widthLine = ""; - } } - if (lines !== board.h) throw new Error("board height error"); + if (lines + 1 !== board.h) throw new Error("board height error"); } 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", () => { diff --git a/types/board.ts b/types/board.ts index 4fa2ef7..c4636be 100644 --- a/types/board.ts +++ b/types/board.ts @@ -11,14 +11,14 @@ export interface FullBoard { /** The total height of the board, in `ch`. */ h: number; - /** Compact array of characters on board. */ - ch: string[]; - /** Compact array of color, for each character. */ - color: string[]; - /** Compact array of background color, for each character. */ - bg_color: string[]; - /** Compact array of width indicator for each character. */ - width: number[]; + /** Compact string of characters on board. */ + ch: string; + /** Compact string of color, for each character. */ + color: string; + /** Compact string of background color, for each character. */ + bg_color: string; + /** Compact string of width indicator for each character. */ + width: string; } /**