From 647ae91469e6360216bcc506b90d127010c68091 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 27 Sep 2026 21:13:58 +0900 Subject: [PATCH] =?UTF-8?q?feat(spreadsheet):=203=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=E2=80=94=20=EC=9E=90=EB=8F=99=20=ED=95=A9=EA=B3=84(Alt+=3D?= =?UTF-8?q?=C2=B7=CE=A3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spreadsheet_autosum.ts 새로 만듦 — 위 또는 왼쪽 연속 숫자 범위를 찾아 =SUM() 식을 활성 칸에 넣음(엑셀 규칙), 여러 칸을 고른 채면 그 안에서만 찾음. 계약 안 건드림 · 엔진 A·격자 C 안 건드림 — 실제 단축키·Σ 단추 이음은 sub7 몫(머리 주석에 적음). 시험 resources/tester/spreadsheet/test_stage3_autosum.ts 6개 통과. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EUypcnp5d1gU2aeKh9F2H7 --- A00_Common/spreadsheet/spreadsheet_autosum.ts | 70 +++++++++++++++++++ .../tester/spreadsheet/test_stage3_autosum.ts | 60 ++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 A00_Common/spreadsheet/spreadsheet_autosum.ts create mode 100644 resources/tester/spreadsheet/test_stage3_autosum.ts diff --git a/A00_Common/spreadsheet/spreadsheet_autosum.ts b/A00_Common/spreadsheet/spreadsheet_autosum.ts new file mode 100644 index 00000000..defa1eda --- /dev/null +++ b/A00_Common/spreadsheet/spreadsheet_autosum.ts @@ -0,0 +1,70 @@ +/* ============================================================================= + * spreadsheet_autosum.ts (3단계 · 주인 sub_laptop_4) + * 자동 합계(Alt+= · 도구 단추 Σ) — 엑셀 규칙: 활성 칸 바로 위가 숫자면 그 위 연속 숫자 칸을 + * 위로 죽 잡고, 아니면 바로 왼쪽이 숫자면 왼쪽으로 죽 잡음. 여러 칸을 고른 채면 그 고름 + * 안에서(활성 칸 위 또는 왼쪽만) 잡음. 아무것도 못 찾으면 빈 `=SUM()`. + * + * 잇는 법(D · E · sub7 — 지금은 하나도 안 이어짐): + * 1) `spreadsheet_keys.ts` 에 Alt+= 를 매핑해 `applyAutoSum(ctx)` 를 부름. + * 2) `spreadsheet_toolbar.ts` 에 Σ 단추를 두고 눌리면 `applyAutoSum(ctx)` 를 부름. + * 3) 지금은 칸에 식 글만 바로 넣음(편집기를 안 거침) — 사용자가 범위를 눈으로 고쳐 잡게 하려면 + * `ctx.editor.begin("=SUM(...)")` 로 편집 상태로 열고 커밋은 사용자가 Enter. + * ========================================================================== */ + +import { rangeToA1, toA1 } from "./spreadsheet_address"; +import { isFrac } from "./spreadsheet_functions_math"; +import type { CellRange } from "./spreadsheet_types"; +import type { SpreadsheetContext } from "./spreadsheet_view_types"; + +function isNumericCell(ctx: SpreadsheetContext, sheet: string, r: number, c: number): boolean { + if (r < 0 || c < 0) return false; + return isFrac(ctx.engine.value(sheet, r, c)); +} + +function verticalRun( + ctx: SpreadsheetContext, + sheet: string, + r: number, + c: number, + top: number, +): CellRange | null { + if (r - 1 < top || !isNumericCell(ctx, sheet, r - 1, c)) return null; + let start = r - 1; + while (start - 1 >= top && isNumericCell(ctx, sheet, start - 1, c)) start--; + return { r0: start, c0: c, r1: r - 1, c1: c }; +} + +function horizontalRun( + ctx: SpreadsheetContext, + sheet: string, + r: number, + c: number, + left: number, +): CellRange | null { + if (c - 1 < left || !isNumericCell(ctx, sheet, r, c - 1)) return null; + let start = c - 1; + while (start - 1 >= left && isNumericCell(ctx, sheet, r, start - 1)) start--; + return { r0: r, c0: start, r1: r, c1: c - 1 }; +} + +/** 활성 칸 기준 합산 범위를 찾음 — 없으면 null(빈 `=SUM()`). */ +export function autoSumRange(ctx: SpreadsheetContext): CellRange | null { + const sheet = ctx.selection.시트; + const { r, c } = ctx.selection.활성; + const sel = ctx.selection.범위[0]; + + if (sel && (sel.r1 > sel.r0 || sel.c1 > sel.c0)) { + if (r > sel.r0) return { r0: sel.r0, c0: c, r1: r - 1, c1: c }; + if (c > sel.c0) return { r0: r, c0: sel.c0, r1: r, c1: c - 1 }; + } + + return verticalRun(ctx, sheet, r, c, 0) ?? horizontalRun(ctx, sheet, r, c, 0); +} + +/** 활성 칸에 `=SUM(...)` 을 바로 넣음(칸 명령 — 편집기를 안 거침). */ +export function applyAutoSum(ctx: SpreadsheetContext): void { + const range = autoSumRange(ctx); + const { r, c } = ctx.selection.활성; + const 식 = range ? `SUM(${rangeToA1(range)})` : "SUM()"; + ctx.dispatch({ 종류: "칸", 시트: ctx.selection.시트, 칸: { [toA1(r, c)]: { 식 } } }); +} diff --git a/resources/tester/spreadsheet/test_stage3_autosum.ts b/resources/tester/spreadsheet/test_stage3_autosum.ts new file mode 100644 index 00000000..f0736521 --- /dev/null +++ b/resources/tester/spreadsheet/test_stage3_autosum.ts @@ -0,0 +1,60 @@ +/* ============================================================================= + * test_stage3_autosum.ts — spreadsheet_autosum 시험. 돌리기: npx tsx <이 파일>. + * ========================================================================== */ + +import { applyAutoSum, autoSumRange } from "../../../A00_Common/spreadsheet/spreadsheet_autosum.ts"; +import { assertEqual, fracLiteral, makeBook, makeCtx, makeSheet } from "./test_stage2_helpers.ts"; + +// 위로 연속 숫자 셋 — A1,A2,A3 숫자 · A4(활성) 빈칸 +const sheet = makeSheet("s1", {}); +const book = makeBook([sheet]); +const ctxUp = makeCtx(book, "s1", { + s1: { A1: fracLiteral(1), A2: fracLiteral(2), A3: fracLiteral(3) }, +}); +ctxUp.selection.활성 = { r: 3, c: 0 }; +ctxUp.selection.범위 = [{ r0: 3, c0: 0, r1: 3, c1: 0 }]; +assertEqual(autoSumRange(ctxUp), { r0: 0, c0: 0, r1: 2, c1: 0 }, "위로 연속 숫자를 잡음"); + +applyAutoSum(ctxUp); +assertEqual( + ctxUp.applied.at(-1), + { 종류: "칸", 시트: "s1", 칸: { A4: { 식: "SUM(A1:A3)" } } }, + "applyAutoSum 이 칸 명령을 냄", +); + +// 위가 빈칸 · 왼쪽으로 연속 숫자 — B1,C1 숫자 · D1(활성) +const ctxLeft = makeCtx(book, "s1", { + s1: { B1: fracLiteral(5), C1: fracLiteral(7) }, +}); +ctxLeft.selection.활성 = { r: 0, c: 3 }; +ctxLeft.selection.범위 = [{ r0: 0, c0: 3, r1: 0, c1: 3 }]; +assertEqual( + autoSumRange(ctxLeft), + { r0: 0, c0: 1, r1: 0, c1: 2 }, + "위가 없으면 왼쪽 연속 숫자를 잡음", +); + +// 위 · 왼쪽 다 없음 — null +const ctxNone = makeCtx(book, "s1"); +ctxNone.selection.활성 = { r: 5, c: 5 }; +ctxNone.selection.범위 = [{ r0: 5, c0: 5, r1: 5, c1: 5 }]; +assertEqual(autoSumRange(ctxNone), null, "위 · 왼쪽 다 없으면 null"); +applyAutoSum(ctxNone); +assertEqual( + ctxNone.applied.at(-1), + { 종류: "칸", 시트: "s1", 칸: { F6: { 식: "SUM()" } } }, + "찾은 범위가 없으면 빈 SUM()", +); + +// 여러 칸을 고른 채 — 활성이 맨 아래일 때 그 고름 안 위쪽만 잡음(가운데 빈칸이 있어도) +const ctxSel = makeCtx(book, "s1", { s1: { A10: fracLiteral(1), A11: fracLiteral(2) } }); +ctxSel.selection.활성 = { r: 11, c: 0 }; +ctxSel.selection.범위 = [{ r0: 9, c0: 0, r1: 11, c1: 0 }]; +assertEqual( + autoSumRange(ctxSel), + { r0: 9, c0: 0, r1: 10, c1: 0 }, + "고른 범위 안에서 활성 위만 잡음", +); + +if (process.exitCode) process.exit(process.exitCode); +console.log("spreadsheet_autosum 시험 끝");