Keep 'carry' for the opaque carry: holdsOnlyAttributes, spellEdgeWhitespace, bothCommonMark
This commit is contained in:
+1
-1
@@ -51,7 +51,7 @@ export function attributeNestingMessage(key: string, type: string): string {
|
|||||||
return `the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels an attribute carries`
|
return `the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels an attribute carries`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function carriesOnly(node: AdfNode, attributes: readonly string[]): boolean {
|
export function holdsOnlyAttributes(node: AdfNode, attributes: readonly string[]): boolean {
|
||||||
if (nodeMarks(node).length > 0 || node.text !== undefined || emptyKeys(node).length > 0) return false
|
if (nodeMarks(node).length > 0 || node.text !== undefined || emptyKeys(node).length > 0) return false
|
||||||
return holdsOnly(nodeAttrs(node), attributes)
|
return holdsOnly(nodeAttrs(node), attributes)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { AdfDocument, AdfNode } from '../../adf/document.ts'
|
import type { AdfDocument, AdfNode } from '../../adf/document.ts'
|
||||||
import type { BlockNodeModel } from '../../adf/block-nodes.ts'
|
import type { BlockNodeModel } from '../../adf/block-nodes.ts'
|
||||||
import type { Flavour } from '../plain/conventions.ts'
|
import type { Flavour } from '../plain/conventions.ts'
|
||||||
import { adfDocumentFault, carriesOnly, isPlainText, nodeAttrs, nodeContent } from '../../adf/document.ts'
|
import { adfDocumentFault, holdsOnlyAttributes, isPlainText, nodeAttrs, nodeContent } from '../../adf/document.ts'
|
||||||
import { alertMarker, foldedAlertMarker, leadingMarker, readAlertMarker, readTaskMarker, taskMarker } from '../plain/conventions.ts'
|
import { alertMarker, foldedAlertMarker, leadingMarker, readAlertMarker, readTaskMarker, taskMarker } from '../plain/conventions.ts'
|
||||||
import { blockDirectiveForm, documentSpelling, listBreakSpelling } from '../block-directive.ts'
|
import { blockDirectiveForm, documentSpelling, listBreakSpelling } from '../block-directive.ts'
|
||||||
import { blockNodeModel, blockNodes } from '../../adf/block-nodes.ts'
|
import { blockNodeModel, blockNodes } from '../../adf/block-nodes.ts'
|
||||||
@@ -76,15 +76,15 @@ function joinBlocks(blocks: readonly PlacedBlock[], container: BlockContainer):
|
|||||||
}
|
}
|
||||||
|
|
||||||
function separationBetween(previous: PlacedBlock, next: PlacedBlock, container: BlockContainer): string {
|
function separationBetween(previous: PlacedBlock, next: PlacedBlock, container: BlockContainer): string {
|
||||||
const plainPair = previous.spelling !== 'directive' && next.spelling !== 'directive'
|
const bothCommonMark = previous.spelling !== 'directive' && next.spelling !== 'directive'
|
||||||
if (plainPair && next.spelling === 'list') {
|
if (bothCommonMark && next.spelling === 'list') {
|
||||||
if (previous.spelling === 'list' && previous.node.type === next.node.type) {
|
if (previous.spelling === 'list' && previous.node.type === next.node.type) {
|
||||||
const gap = container === 'directive' ? '\n' : '\n\n'
|
const gap = container === 'directive' ? '\n' : '\n\n'
|
||||||
return `${gap}${listBreakSpelling}${gap}`
|
return `${gap}${listBreakSpelling}${gap}`
|
||||||
}
|
}
|
||||||
if (container === 'list-item') return interruptsParagraph(next.node) ? '\n' : '\n\n'
|
if (container === 'list-item') return interruptsParagraph(next.node) ? '\n' : '\n\n'
|
||||||
}
|
}
|
||||||
return container === 'directive' && !plainPair ? '\n' : '\n\n'
|
return container === 'directive' && !bothCommonMark ? '\n' : '\n\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
function interruptsParagraph(node: AdfNode): boolean {
|
function interruptsParagraph(node: AdfNode): boolean {
|
||||||
@@ -243,7 +243,7 @@ function emitDirectiveBody(node: AdfNode, model: BlockNodeModel, opener: string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
function tryBlockquote(node: AdfNode, path: ConvertErrorPath, depth: number, writing: Writing): Result<EmittedBlock> | undefined {
|
function tryBlockquote(node: AdfNode, path: ConvertErrorPath, depth: number, writing: Writing): Result<EmittedBlock> | undefined {
|
||||||
if (!carriesOnly(node, [])) return undefined
|
if (!holdsOnlyAttributes(node, [])) return undefined
|
||||||
const inner = walkBlocks(nodeContent(node), path, depth + 1, writing)
|
const inner = walkBlocks(nodeContent(node), path, depth + 1, writing)
|
||||||
if (!inner.ok) return inner
|
if (!inner.ok) return inner
|
||||||
const text = joinBlocks(inner.value.blocks, 'document')
|
const text = joinBlocks(inner.value.blocks, 'document')
|
||||||
@@ -252,7 +252,7 @@ function tryBlockquote(node: AdfNode, path: ConvertErrorPath, depth: number, wri
|
|||||||
}
|
}
|
||||||
|
|
||||||
function tryCodeBlock(node: AdfNode, path: ConvertErrorPath): Result<EmittedBlock> | undefined {
|
function tryCodeBlock(node: AdfNode, path: ConvertErrorPath): Result<EmittedBlock> | undefined {
|
||||||
if (!carriesOnly(node, ['language'])) return undefined
|
if (!holdsOnlyAttributes(node, ['language'])) return undefined
|
||||||
const slot = languageSlot(nodeAttrs(node)['language'])
|
const slot = languageSlot(nodeAttrs(node)['language'])
|
||||||
if (slot.kind === 'attribute') return undefined
|
if (slot.kind === 'attribute') return undefined
|
||||||
const texts = fencedTexts(node, path)
|
const texts = fencedTexts(node, path)
|
||||||
@@ -291,7 +291,7 @@ function fencedTexts(node: AdfNode, path: ConvertErrorPath): Result<string[] | u
|
|||||||
}
|
}
|
||||||
|
|
||||||
function tryHeading(node: AdfNode, path: ConvertErrorPath, flavour: Flavour): Result<EmittedBlock> | undefined {
|
function tryHeading(node: AdfNode, path: ConvertErrorPath, flavour: Flavour): Result<EmittedBlock> | undefined {
|
||||||
if (!carriesOnly(node, ['level'])) return undefined
|
if (!holdsOnlyAttributes(node, ['level'])) return undefined
|
||||||
const level = nodeAttrs(node)['level']
|
const level = nodeAttrs(node)['level']
|
||||||
if (typeof level !== 'number' || !Number.isInteger(level) || level < 1 || level > 6) return undefined
|
if (typeof level !== 'number' || !Number.isInteger(level) || level < 1 || level > 6) return undefined
|
||||||
const hashes = '#'.repeat(level)
|
const hashes = '#'.repeat(level)
|
||||||
@@ -304,11 +304,11 @@ function tryHeading(node: AdfNode, path: ConvertErrorPath, flavour: Flavour): Re
|
|||||||
|
|
||||||
function tryList(node: AdfNode, path: ConvertErrorPath, depth: number, writing: Writing): Result<EmittedBlock> | undefined {
|
function tryList(node: AdfNode, path: ConvertErrorPath, depth: number, writing: Writing): Result<EmittedBlock> | undefined {
|
||||||
const ordered = node.type === 'orderedList'
|
const ordered = node.type === 'orderedList'
|
||||||
if (!carriesOnly(node, ordered ? ['order'] : [])) return undefined
|
if (!holdsOnlyAttributes(node, ordered ? ['order'] : [])) return undefined
|
||||||
const items = nodeContent(node)
|
const items = nodeContent(node)
|
||||||
const start = listStart(node, items.length)
|
const start = listStart(node, items.length)
|
||||||
if (start === undefined || items.length === 0) return undefined
|
if (start === undefined || items.length === 0) return undefined
|
||||||
if (items.some((item) => item.type !== 'listItem' || !carriesOnly(item, []))) return undefined
|
if (items.some((item) => item.type !== 'listItem' || !holdsOnlyAttributes(item, []))) return undefined
|
||||||
const walked: WalkedItem[] = []
|
const walked: WalkedItem[] = []
|
||||||
let headroom = Number.POSITIVE_INFINITY
|
let headroom = Number.POSITIVE_INFINITY
|
||||||
for (const [offset, item] of items.entries()) {
|
for (const [offset, item] of items.entries()) {
|
||||||
@@ -356,13 +356,13 @@ function tryListItemLines(inner: string, marker: string): string | undefined {
|
|||||||
|
|
||||||
function tryParagraph(node: AdfNode, path: ConvertErrorPath, flavour: Flavour): Result<EmittedBlock> | undefined {
|
function tryParagraph(node: AdfNode, path: ConvertErrorPath, flavour: Flavour): Result<EmittedBlock> | undefined {
|
||||||
const content = nodeContent(node)
|
const content = nodeContent(node)
|
||||||
if (content.length === 0 || !carriesOnly(node, [])) return undefined
|
if (content.length === 0 || !holdsOnlyAttributes(node, [])) return undefined
|
||||||
const line = emitInlineLine(content, 'paragraph', path, flavour)
|
const line = emitInlineLine(content, 'paragraph', path, flavour)
|
||||||
if (!line.ok) return line
|
if (!line.ok) return line
|
||||||
return success(commonMarkText(line.value))
|
return success(commonMarkText(line.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
function tryRule(node: AdfNode): string | undefined {
|
function tryRule(node: AdfNode): string | undefined {
|
||||||
if (!carriesOnly(node, []) || nodeContent(node).length > 0) return undefined
|
if (!holdsOnlyAttributes(node, []) || nodeContent(node).length > 0) return undefined
|
||||||
return '---'
|
return '---'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { AdfNode } from '../../adf/document.ts'
|
import type { AdfNode } from '../../adf/document.ts'
|
||||||
import type { ConvertErrorPath } from '../../result.ts'
|
import type { ConvertErrorPath } from '../../result.ts'
|
||||||
import { carriesOnly, nodeAttrs, nodeContent } from '../../adf/document.ts'
|
import { holdsOnlyAttributes, nodeAttrs, nodeContent } from '../../adf/document.ts'
|
||||||
import { serializeCanonicalJson } from '../../canonical-json.ts'
|
import { serializeCanonicalJson } from '../../canonical-json.ts'
|
||||||
import { tryImageLine } from './inline-line.ts'
|
import { tryImageLine } from './inline-line.ts'
|
||||||
|
|
||||||
@@ -16,8 +16,8 @@ export function tryImage(node: AdfNode, path: ConvertErrorPath): string | undefi
|
|||||||
function imageShape(node: AdfNode): { alt: string | undefined; url: string } | undefined {
|
function imageShape(node: AdfNode): { alt: string | undefined; url: string } | undefined {
|
||||||
const content = nodeContent(node)
|
const content = nodeContent(node)
|
||||||
const media = content[0]
|
const media = content[0]
|
||||||
if (!carriesOnly(node, ['layout']) || serializeCanonicalJson(nodeAttrs(node), 'compact') !== centeredMediaSingle) return undefined
|
if (!holdsOnlyAttributes(node, ['layout']) || serializeCanonicalJson(nodeAttrs(node), 'compact') !== centeredMediaSingle) return undefined
|
||||||
if (media === undefined || content.length !== 1 || media.type !== 'media' || !carriesOnly(media, imageAttributes) || nodeContent(media).length > 0) return undefined
|
if (media === undefined || content.length !== 1 || media.type !== 'media' || !holdsOnlyAttributes(media, imageAttributes) || nodeContent(media).length > 0) return undefined
|
||||||
const attrs = nodeAttrs(media)
|
const attrs = nodeAttrs(media)
|
||||||
const alt = attrs['alt']
|
const alt = attrs['alt']
|
||||||
const url = attrs['url']
|
const url = attrs['url']
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ function lineSegments(nodes: readonly AdfNode[], container: LineContainer, path:
|
|||||||
const emission = emitRun(nodes, 0, 0, context)
|
const emission = emitRun(nodes, 0, 0, context)
|
||||||
if (!emission.ok) return emission
|
if (!emission.ok) return emission
|
||||||
if (emission.value.carry !== undefined) return emission
|
if (emission.value.carry !== undefined) return emission
|
||||||
return success({ segments: carryStrippedWhitespace(emission.value.segments) })
|
return success({ segments: spellEdgeWhitespace(emission.value.segments) })
|
||||||
}
|
}
|
||||||
|
|
||||||
function attemptLine(segments: readonly InlineSegment[], container: LineContainer, path: ConvertErrorPath, flavour: Flavour): Result<LineAttempt> {
|
function attemptLine(segments: readonly InlineSegment[], container: LineContainer, path: ConvertErrorPath, flavour: Flavour): Result<LineAttempt> {
|
||||||
@@ -137,32 +137,32 @@ function lineVerdict(segments: readonly InlineSegment[], container: LineContaine
|
|||||||
}
|
}
|
||||||
|
|
||||||
// spec/flavour.md, Inline nodes.
|
// spec/flavour.md, Inline nodes.
|
||||||
function carryStrippedWhitespace(segments: readonly InlineSegment[]): InlineSegment[] {
|
function spellEdgeWhitespace(segments: readonly InlineSegment[]): InlineSegment[] {
|
||||||
const carried: InlineSegment[] = []
|
const spelled: InlineSegment[] = []
|
||||||
for (const [index, segment] of segments.entries()) {
|
for (const [index, segment] of segments.entries()) {
|
||||||
const previous = segments[index - 1]
|
const previous = segments[index - 1]
|
||||||
const next = segments[index + 1]
|
const next = segments[index + 1]
|
||||||
const leading = previous === undefined || previous.text.includes('\n')
|
const leading = previous === undefined || previous.text.includes('\n')
|
||||||
const trailing = next === undefined || next.text.includes('\n')
|
const trailing = next === undefined || next.text.includes('\n')
|
||||||
carried.push(...carryEdges(segment, leading, trailing))
|
spelled.push(...edgeWhitespaceSegments(segment, leading, trailing))
|
||||||
}
|
}
|
||||||
return carried
|
return spelled
|
||||||
}
|
}
|
||||||
|
|
||||||
function carryEdges(segment: InlineSegment, leading: boolean, trailing: boolean): InlineSegment[] {
|
function edgeWhitespaceSegments(segment: InlineSegment, leading: boolean, trailing: boolean): InlineSegment[] {
|
||||||
if (segment.escaping !== 'backslash' && segment.escaping !== 'bracketed') return [segment]
|
if (segment.escaping !== 'backslash' && segment.escaping !== 'bracketed') return [segment]
|
||||||
const head = leading ? (/^[ \t]+/.exec(segment.text)?.[0] ?? '') : ''
|
const head = leading ? (/^[ \t]+/.exec(segment.text)?.[0] ?? '') : ''
|
||||||
const body = segment.text.slice(head.length)
|
const body = segment.text.slice(head.length)
|
||||||
const middle = trailing ? trimTrailingSpace(body) : body
|
const middle = trailing ? trimTrailingSpace(body) : body
|
||||||
const tail = body.slice(middle.length)
|
const tail = body.slice(middle.length)
|
||||||
const edges: InlineSegment[] = []
|
const edges: InlineSegment[] = []
|
||||||
if (head !== '') edges.push(carriedText(head))
|
if (head !== '') edges.push(textDirectiveSegment(head))
|
||||||
if (middle !== '') edges.push({ escaping: segment.escaping, text: middle })
|
if (middle !== '') edges.push({ escaping: segment.escaping, text: middle })
|
||||||
if (tail !== '') edges.push(carriedText(tail))
|
if (tail !== '') edges.push(textDirectiveSegment(tail))
|
||||||
return edges
|
return edges
|
||||||
}
|
}
|
||||||
|
|
||||||
function carriedText(text: string): InlineSegment {
|
function textDirectiveSegment(text: string): InlineSegment {
|
||||||
return syntax(spellTextDirective(text))
|
return syntax(spellTextDirective(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +275,7 @@ function emitText(node: AdfNode, context: InlineContext, index: number, path: Co
|
|||||||
if (holdsNullCharacter(node.text)) return failure('unspellable-character', 'a text node holds a null character CommonMark replaces', path)
|
if (holdsNullCharacter(node.text)) return failure('unspellable-character', 'a text node holds a null character CommonMark replaces', path)
|
||||||
const escaping: InlineEscaping = context.bracketed ? 'bracketed' : 'backslash'
|
const escaping: InlineEscaping = context.bracketed ? 'bracketed' : 'backslash'
|
||||||
const parts = node.text.split(/(\n+)/).filter((part) => part !== '')
|
const parts = node.text.split(/(\n+)/).filter((part) => part !== '')
|
||||||
return success({ segments: parts.map((part) => (part.startsWith('\n') ? carriedText(part) : { escaping, text: part })) })
|
return success({ segments: parts.map((part) => (part.startsWith('\n') ? textDirectiveSegment(part) : { escaping, text: part })) })
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitMarkedRun(nodes: readonly AdfNode[], mark: AdfMark, depth: number, index: number, context: InlineContext): Result<Emission> {
|
function emitMarkedRun(nodes: readonly AdfNode[], mark: AdfMark, depth: number, index: number, context: InlineContext): Result<Emission> {
|
||||||
@@ -300,11 +300,11 @@ function emitEmphasis(nodes: readonly AdfNode[], spelling: string, depth: number
|
|||||||
const inner = emitRun(nodes, depth + 1, range.first, context)
|
const inner = emitRun(nodes, depth + 1, range.first, context)
|
||||||
if (!inner.ok) return inner
|
if (!inner.ok) return inner
|
||||||
if (inner.value.carry !== undefined) return inner
|
if (inner.value.carry !== undefined) return inner
|
||||||
const carried = carryStrippedWhitespace(inner.value.segments)
|
const spelled = spellEdgeWhitespace(inner.value.segments)
|
||||||
return success({
|
return success({
|
||||||
segments: [
|
segments: [
|
||||||
{ emphasis: 'open', escaping: 'none', nodes: { ...range, depth }, text: spelling },
|
{ emphasis: 'open', escaping: 'none', nodes: { ...range, depth }, text: spelling },
|
||||||
...carried,
|
...spelled,
|
||||||
{ emphasis: 'close', escaping: 'none', nodes: { ...range, depth }, text: spelling },
|
{ emphasis: 'close', escaping: 'none', nodes: { ...range, depth }, text: spelling },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { AdfNode } from '../../adf/document.ts'
|
import type { AdfNode } from '../../adf/document.ts'
|
||||||
import type { ConvertErrorPath } from '../../result.ts'
|
import type { ConvertErrorPath } from '../../result.ts'
|
||||||
import type { Flavour } from '../plain/conventions.ts'
|
import type { Flavour } from '../plain/conventions.ts'
|
||||||
import { carriesOnly, nodeContent } from '../../adf/document.ts'
|
import { holdsOnlyAttributes, nodeContent } from '../../adf/document.ts'
|
||||||
import { spellPipeDelimiter, spellPipeRow } from '../pipe-table-syntax.ts'
|
import { spellPipeDelimiter, spellPipeRow } from '../pipe-table-syntax.ts'
|
||||||
import { tryPipeCell } from './inline-line.ts'
|
import { tryPipeCell } from './inline-line.ts'
|
||||||
|
|
||||||
@@ -26,16 +26,16 @@ export function tryPipeTable(node: AdfNode, path: ConvertErrorPath, flavour: Fla
|
|||||||
function pipeRows(node: AdfNode): AdfNode[][] | undefined {
|
function pipeRows(node: AdfNode): AdfNode[][] | undefined {
|
||||||
const rows = nodeContent(node)
|
const rows = nodeContent(node)
|
||||||
const columns = rows[0] === undefined ? 0 : nodeContent(rows[0]).length
|
const columns = rows[0] === undefined ? 0 : nodeContent(rows[0]).length
|
||||||
if (!carriesOnly(node, []) || columns === 0) return undefined
|
if (!holdsOnlyAttributes(node, []) || columns === 0) return undefined
|
||||||
const grid: AdfNode[][] = []
|
const grid: AdfNode[][] = []
|
||||||
for (const [index, row] of rows.entries()) {
|
for (const [index, row] of rows.entries()) {
|
||||||
const cells = nodeContent(row)
|
const cells = nodeContent(row)
|
||||||
if (row.type !== 'tableRow' || !carriesOnly(row, []) || cells.length !== columns) return undefined
|
if (row.type !== 'tableRow' || !holdsOnlyAttributes(row, []) || cells.length !== columns) return undefined
|
||||||
const wanted = index === 0 ? 'tableHeader' : 'tableCell'
|
const wanted = index === 0 ? 'tableHeader' : 'tableCell'
|
||||||
const paragraphs: AdfNode[] = []
|
const paragraphs: AdfNode[] = []
|
||||||
for (const cell of cells) {
|
for (const cell of cells) {
|
||||||
const paragraph = plainParagraph(cell)
|
const paragraph = plainParagraph(cell)
|
||||||
if (paragraph === undefined || cell.type !== wanted || !carriesOnly(cell, [])) return undefined
|
if (paragraph === undefined || cell.type !== wanted || !holdsOnlyAttributes(cell, [])) return undefined
|
||||||
paragraphs.push(paragraph)
|
paragraphs.push(paragraph)
|
||||||
}
|
}
|
||||||
grid.push(paragraphs)
|
grid.push(paragraphs)
|
||||||
@@ -46,6 +46,6 @@ function pipeRows(node: AdfNode): AdfNode[][] | undefined {
|
|||||||
function plainParagraph(cell: AdfNode): AdfNode | undefined {
|
function plainParagraph(cell: AdfNode): AdfNode | undefined {
|
||||||
const content = nodeContent(cell)
|
const content = nodeContent(cell)
|
||||||
const paragraph = content[0]
|
const paragraph = content[0]
|
||||||
if (paragraph === undefined || content.length !== 1 || paragraph.type !== 'paragraph' || !carriesOnly(paragraph, [])) return undefined
|
if (paragraph === undefined || content.length !== 1 || paragraph.type !== 'paragraph' || !holdsOnlyAttributes(paragraph, [])) return undefined
|
||||||
return paragraph
|
return paragraph
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user