mirror of
https://github.com/TextPlace/CoreTextPlace.git
synced 2025-04-30 20:51:11 +00:00
Compare commits
9 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7923680e80 | ||
![]() |
4dd8121ebb | ||
![]() |
8940f26f17 | ||
![]() |
97ad480ad8 | ||
![]() |
645b720222 | ||
![]() |
46a647441b | ||
![]() |
587e0558e9 | ||
![]() |
91db478fe9 | ||
![]() |
5077995eb3 |
10 changed files with 150 additions and 71 deletions
|
@ -1,9 +1,10 @@
|
|||
// Static tasks configuration.
|
||||
//
|
||||
// Example:
|
||||
[
|
||||
{
|
||||
"label": "Test",
|
||||
"command": "deno test"
|
||||
},
|
||||
{
|
||||
"label": "Test - Watch",
|
||||
"command": "deno test --watch"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
{
|
||||
"name": "@textplace/core",
|
||||
"version": "0.2.0",
|
||||
"version": "0.4.1",
|
||||
"exports": "./mod.ts",
|
||||
"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
7
deno.lock
generated
|
@ -7,6 +7,7 @@
|
|||
"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",
|
||||
|
@ -54,6 +55,9 @@
|
|||
"@std/bytes@0.223.0": {
|
||||
"integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8"
|
||||
},
|
||||
"@std/cli@1.0.11": {
|
||||
"integrity": "ec219619fdcd31bcf0d8e53bee1e2706ec9a02f70255365a094f69755dadd340"
|
||||
},
|
||||
"@std/fmt@0.223.0": {
|
||||
"integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208"
|
||||
},
|
||||
|
@ -151,7 +155,8 @@
|
|||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@deno/dnt@~0.41.3"
|
||||
"jsr:@deno/dnt@~0.41.3",
|
||||
"jsr:@std/cli@^1.0.11"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,15 +9,7 @@ import { applyChange, createSection } from "./section.ts";
|
|||
import type { BoardChange } from "../types/change.ts";
|
||||
|
||||
export function createBoard(config: BoardConfig): BoardData {
|
||||
const sections: SectionData[][] = Array(config.ySections)
|
||||
.fill(0)
|
||||
.map((_, sy) =>
|
||||
Array(config.xSections)
|
||||
.fill(0)
|
||||
.map((_, sx) => createSection({ sx, sy }, config))
|
||||
);
|
||||
|
||||
return { config, sections };
|
||||
return { config, sections: [] };
|
||||
}
|
||||
|
||||
export function locateSection(
|
||||
|
@ -30,9 +22,38 @@ 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 { sx, sy } = locateSection(change, board.config);
|
||||
const section = board.sections[sy][sx];
|
||||
const sPos = locateSection(change, board.config);
|
||||
const section = getSectionOnBoard(sPos, board);
|
||||
applyChange(change, section);
|
||||
}
|
||||
|
||||
|
@ -59,8 +80,8 @@ export function renderFullBoard(data: BoardData): FullBoard {
|
|||
continue;
|
||||
}
|
||||
|
||||
const { sx, sy } = locateSection({ x, y }, data.config);
|
||||
const section = data.sections[sy][sx];
|
||||
const sPos = locateSection({ x, y }, data.config);
|
||||
const section = getSectionOnBoard(sPos, data, { readOnly: true });
|
||||
const xInSection = x % data.config.sectionWidth;
|
||||
const yInSection = y % data.config.sectionHeight;
|
||||
|
||||
|
@ -85,9 +106,9 @@ export function renderFullBoard(data: BoardData): FullBoard {
|
|||
return {
|
||||
w: lineLength,
|
||||
h: totalLineCount,
|
||||
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(),
|
||||
ch: chLines.flat(),
|
||||
color: colorLines.flat(),
|
||||
bg_color: bgColorLines.flat(),
|
||||
width: widthLines.flat(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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)];
|
||||
|
@ -11,11 +10,6 @@ export function getCharacterWidth(ch: string): number {
|
|||
);
|
||||
}
|
||||
|
||||
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;
|
||||
// TODO: Properly fix this.
|
||||
return Math.min(unicodeWidth(ch), 2);
|
||||
}
|
||||
|
|
|
@ -16,17 +16,17 @@ export function createSection(
|
|||
const offsetX = sx * boardConfig.sectionWidth;
|
||||
const offsetY = sy * boardConfig.sectionHeight;
|
||||
|
||||
const ch: string[][] = Array(boardConfig.sectionHeight).fill(
|
||||
Array(boardConfig.sectionWidth).fill(boardConfig.defaultCh),
|
||||
const ch: string[][] = Array(boardConfig.sectionHeight).fill([]).map(() =>
|
||||
Array(boardConfig.sectionWidth).fill(boardConfig.defaultCh)
|
||||
);
|
||||
const color: string[][] = Array(boardConfig.sectionHeight).fill(
|
||||
Array(boardConfig.sectionWidth).fill(boardConfig.defaultColor),
|
||||
const color: string[][] = Array(boardConfig.sectionHeight).fill([]).map(() =>
|
||||
Array(boardConfig.sectionWidth).fill(boardConfig.defaultColor)
|
||||
);
|
||||
const bgColor: string[][] = Array(boardConfig.sectionHeight).fill(
|
||||
Array(boardConfig.sectionWidth).fill(boardConfig.defaultBgColor),
|
||||
const bgColor: string[][] = Array(boardConfig.sectionHeight).fill([]).map(
|
||||
() => Array(boardConfig.sectionWidth).fill(boardConfig.defaultBgColor),
|
||||
);
|
||||
const width: number[][] = Array(boardConfig.sectionHeight).fill(
|
||||
Array(boardConfig.sectionWidth).fill(boardConfig.defaultWidth),
|
||||
const width: number[][] = Array(boardConfig.sectionHeight).fill([]).map(() =>
|
||||
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 % 2;
|
||||
const xCharacterOffset = xInSection % chWidth;
|
||||
const offsetAdjustedXInSection = xInSection - xCharacterOffset;
|
||||
section.ch[yInSection][offsetAdjustedXInSection] = change.ch;
|
||||
section.width[yInSection][offsetAdjustedXInSection] = chWidth;
|
||||
|
|
|
@ -3,7 +3,11 @@ import {
|
|||
assertEquals,
|
||||
} from "https://deno.land/std@0.224.0/assert/mod.ts";
|
||||
|
||||
import { createBoard, renderFullBoard } from "../logic/board.ts";
|
||||
import {
|
||||
createBoard,
|
||||
getSectionOnBoard,
|
||||
renderFullBoard,
|
||||
} from "../logic/board.ts";
|
||||
import type { BoardData } from "../types/board.ts";
|
||||
import { checkFullBoard } from "./checkFullBoard.ts";
|
||||
import { locateSection } from "../logic/board.ts";
|
||||
|
@ -14,8 +18,8 @@ Deno.test("board", async (t) => {
|
|||
|
||||
await t.step("createBoard", () => {
|
||||
board = createBoard({
|
||||
xSections: 2,
|
||||
ySections: 2,
|
||||
xSections: 3,
|
||||
ySections: 3,
|
||||
sectionWidth: 4,
|
||||
sectionHeight: 3,
|
||||
defaultCh: " ",
|
||||
|
@ -24,17 +28,8 @@ Deno.test("board", async (t) => {
|
|||
defaultWidth: 1,
|
||||
});
|
||||
|
||||
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);
|
||||
// Sections are created on demand.
|
||||
assertEquals(board.sections.length, 0);
|
||||
});
|
||||
|
||||
await t.step("locateSection", () => {
|
||||
|
@ -56,11 +51,12 @@ 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][0], "D");
|
||||
assertEquals(board.sections[1][1].ch[0], ["D", "E", " ", " "]);
|
||||
|
||||
applyChangeOnBoard({ x: 0, y: 1, ch: "你" }, board);
|
||||
applyChangeOnBoard({ x: 4, y: 2, ch: "好" }, board);
|
||||
|
@ -70,7 +66,46 @@ 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[2][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);
|
||||
});
|
||||
|
||||
await t.step("renderFullBoard", () => {
|
||||
|
@ -79,4 +114,11 @@ 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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,14 +26,18 @@ Deno.test("getCharacterWidth CJK", () => {
|
|||
assertEquals(getCharacterWidth("グ"), 2);
|
||||
assertEquals(getCharacterWidth("ソ"), 2);
|
||||
|
||||
assertThrows(() => getCharacterWidth("?"));
|
||||
assertThrows(() => getCharacterWidth("!"));
|
||||
assertEquals(getCharacterWidth("?"), 2);
|
||||
assertEquals(getCharacterWidth("!"), 2);
|
||||
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]));
|
||||
});
|
||||
|
|
|
@ -47,6 +47,11 @@ 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");
|
||||
|
|
|
@ -90,27 +90,33 @@ Deno.test("section", async (t) => {
|
|||
assert(section);
|
||||
|
||||
applyChange({ x: 0, y: 0, ch: "t" }, section);
|
||||
assertEquals(section.ch[0][0], "t");
|
||||
assertEquals(section.ch[0][1], " ");
|
||||
assertEquals(section.width[0][0], 1);
|
||||
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]);
|
||||
});
|
||||
|
||||
await t.step("applyChange 2-width at a correct position", () => {
|
||||
assert(section);
|
||||
|
||||
applyChange({ x: 0, y: 0, ch: "あ" }, section);
|
||||
assertEquals(section.ch[0][0], "あ");
|
||||
assertEquals(section.ch[0][1], " ");
|
||||
assertEquals(section.width[0][0], 2);
|
||||
assertEquals(section.ch[0], ["あ", "t", " ", " "]);
|
||||
assertEquals(section.width[0], [2, 1, 1, 1]);
|
||||
});
|
||||
|
||||
await t.step("applyChange 2-width at an alternate position", () => {
|
||||
assert(section);
|
||||
|
||||
applyChange({ x: 1, y: 0, ch: "あ" }, section);
|
||||
assertEquals(section.ch[0][0], "あ");
|
||||
assertEquals(section.ch[0][1], " ");
|
||||
assertEquals(section.width[0][0], 2);
|
||||
assertEquals(section.ch[0], ["あ", "t", " ", " "]);
|
||||
assertEquals(section.width[0], [2, 1, 1, 1]);
|
||||
});
|
||||
|
||||
await t.step("applyChange incorrect section", () => {
|
||||
|
|
Loading…
Add table
Reference in a new issue