From 2fb5366952643d3cf742f4f212d431f4b8ccb167 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sun, 18 Oct 2026 12:05:27 +0200 Subject: [PATCH] =?UTF-8?q?Answer=20stability=20round=201:=201=20?= =?UTF-8?q?=F0=9F=94=B4,=202=20=F0=9F=9F=A1;=20read=20a=20quote=20marker?= =?UTF-8?q?=20behind=20a=20split=20tab's=20leftover=20columns=20as=20text,?= =?UTF-8?q?=20and=20pin=20the=20tab=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/markdown/parse/blocks.test.ts | 26 ++++++++++++++++++++++++++ src/markdown/parse/blocks.ts | 7 ++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/markdown/parse/blocks.test.ts b/src/markdown/parse/blocks.test.ts index 0e945e1..c597bdb 100644 --- a/src/markdown/parse/blocks.test.ts +++ b/src/markdown/parse/blocks.test.ts @@ -152,3 +152,29 @@ test('stops a blank line at an empty item, and gives a code block in an item the { kind: 'code', language: '', position: { line: 1, offset: 0 }, text: '\nx' }, ]) }) + +function outline(blocks: readonly Block[]): string { + return blocks + .map((block) => { + if (block.kind === 'blockquote') return `quote(${outline(block.blocks)})` + if (block.kind === 'bulletList') return `list(${block.items.map(outline).join(',')})` + if (block.kind === 'code') return `code(${JSON.stringify(block.text)})` + if (block.kind === 'paragraph') return `paragraph(${block.text.split('\n').map((line) => line.trim()).join('|')})` + return block.kind + }) + .join(' ') +} + +test('reads a marker a tab pushes to four columns past its container as text, not as a marker', () => { + const read = (markdown: string): string => outline(parseBlocks(markdown, flavourClaims.lossless).blocks) + assert.equal(read('> a\n \t> b\n'), 'quote(paragraph(a|> b))') + assert.equal(read('> > a\n>\t\t> b\n'), 'quote(quote(paragraph(a|> b)))') + assert.equal(read('- > a\n\t\t> b\n'), 'list(quote(paragraph(a|> b)))') + assert.equal(read('x\n\t# h\n'), 'paragraph(x|# h)') +}) + +test('gives what follows an item the columns of the tab a cut splits', () => { + const read = (markdown: string): string => outline(parseBlocks(markdown, flavourClaims.lossless).blocks) + assert.equal(read('- ```\n\tx\n ```\n'), 'list(code(" x"))') + assert.equal(read('- a\n \t- b\n'), 'list(paragraph(a) list(paragraph(b)))') +}) diff --git a/src/markdown/parse/blocks.ts b/src/markdown/parse/blocks.ts index ea17138..3e7415f 100644 --- a/src/markdown/parse/blocks.ts +++ b/src/markdown/parse/blocks.ts @@ -63,8 +63,9 @@ type ContainerStart = { kind: 'blockquote'; rest: Line } | { fresh: boolean; ind type ParagraphLine = { position: SourcePosition; text: string } // The line from an absolute column on: a tab a cut splits keeps the stop it is measured against, and its leftover columns -// stand as `spaces` before `text`, so a cut never builds a string. At most 3, which a cut of `largestOpenerIndentation` takes whole. -// They are blank, so blankness reads `text` alone; a block takes the line through `lineText`. +// stand as `spaces` before `text`, so a cut shares `text`. At most 3, and blank, so blankness reads `text` alone. A cut of +// `largestOpenerIndentation` leaves some only on a line indented as code, which no marker follows: a marker check reads +// `spaces` first, and a block takes the line through `lineText`. type Line = { column: number; spaces: number; text: string } type LeafOpener = { index: number; position: SourcePosition } @@ -201,7 +202,7 @@ function continuesContainer(container: EdgeContainer, line: Line): Line | undefi } function blockquoteRest(opener: Line): Line | undefined { - if (!opener.text.startsWith('>')) return undefined + if (opener.spaces > 0 || !opener.text.startsWith('>')) return undefined return removeColumns({ column: opener.column + 1, spaces: 0, text: opener.text.slice(1) }, 1) }