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`
|
||||
}
|
||||
|
||||
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
|
||||
return holdsOnly(nodeAttrs(node), attributes)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AdfDocument, AdfNode } from '../../adf/document.ts'
|
||||
import type { BlockNodeModel } from '../../adf/block-nodes.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 { blockDirectiveForm, documentSpelling, listBreakSpelling } from '../block-directive.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 {
|
||||
const plainPair = previous.spelling !== 'directive' && next.spelling !== 'directive'
|
||||
if (plainPair && next.spelling === 'list') {
|
||||
const bothCommonMark = previous.spelling !== 'directive' && next.spelling !== 'directive'
|
||||
if (bothCommonMark && next.spelling === 'list') {
|
||||
if (previous.spelling === 'list' && previous.node.type === next.node.type) {
|
||||
const gap = container === 'directive' ? '\n' : '\n\n'
|
||||
return `${gap}${listBreakSpelling}${gap}`
|
||||
}
|
||||
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 {
|
||||
@@ -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 {
|
||||
if (!carriesOnly(node, [])) return undefined
|
||||
if (!holdsOnlyAttributes(node, [])) return undefined
|
||||
const inner = walkBlocks(nodeContent(node), path, depth + 1, writing)
|
||||
if (!inner.ok) return inner
|
||||
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 {
|
||||
if (!carriesOnly(node, ['language'])) return undefined
|
||||
if (!holdsOnlyAttributes(node, ['language'])) return undefined
|
||||
const slot = languageSlot(nodeAttrs(node)['language'])
|
||||
if (slot.kind === 'attribute') return undefined
|
||||
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 {
|
||||
if (!carriesOnly(node, ['level'])) return undefined
|
||||
if (!holdsOnlyAttributes(node, ['level'])) return undefined
|
||||
const level = nodeAttrs(node)['level']
|
||||
if (typeof level !== 'number' || !Number.isInteger(level) || level < 1 || level > 6) return undefined
|
||||
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 {
|
||||
const ordered = node.type === 'orderedList'
|
||||
if (!carriesOnly(node, ordered ? ['order'] : [])) return undefined
|
||||
if (!holdsOnlyAttributes(node, ordered ? ['order'] : [])) return undefined
|
||||
const items = nodeContent(node)
|
||||
const start = listStart(node, items.length)
|
||||
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[] = []
|
||||
let headroom = Number.POSITIVE_INFINITY
|
||||
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 {
|
||||
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)
|
||||
if (!line.ok) return line
|
||||
return success(commonMarkText(line.value))
|
||||
}
|
||||
|
||||
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 '---'
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AdfNode } from '../../adf/document.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 { 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 {
|
||||
const content = nodeContent(node)
|
||||
const media = content[0]
|
||||
if (!carriesOnly(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 (!holdsOnlyAttributes(node, ['layout']) || serializeCanonicalJson(nodeAttrs(node), 'compact') !== centeredMediaSingle) 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 alt = attrs['alt']
|
||||
const url = attrs['url']
|
||||
|
||||
@@ -114,7 +114,7 @@ function lineSegments(nodes: readonly AdfNode[], container: LineContainer, path:
|
||||
const emission = emitRun(nodes, 0, 0, context)
|
||||
if (!emission.ok) 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> {
|
||||
@@ -137,32 +137,32 @@ function lineVerdict(segments: readonly InlineSegment[], container: LineContaine
|
||||
}
|
||||
|
||||
// spec/flavour.md, Inline nodes.
|
||||
function carryStrippedWhitespace(segments: readonly InlineSegment[]): InlineSegment[] {
|
||||
const carried: InlineSegment[] = []
|
||||
function spellEdgeWhitespace(segments: readonly InlineSegment[]): InlineSegment[] {
|
||||
const spelled: InlineSegment[] = []
|
||||
for (const [index, segment] of segments.entries()) {
|
||||
const previous = segments[index - 1]
|
||||
const next = segments[index + 1]
|
||||
const leading = previous === undefined || previous.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]
|
||||
const head = leading ? (/^[ \t]+/.exec(segment.text)?.[0] ?? '') : ''
|
||||
const body = segment.text.slice(head.length)
|
||||
const middle = trailing ? trimTrailingSpace(body) : body
|
||||
const tail = body.slice(middle.length)
|
||||
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 (tail !== '') edges.push(carriedText(tail))
|
||||
if (tail !== '') edges.push(textDirectiveSegment(tail))
|
||||
return edges
|
||||
}
|
||||
|
||||
function carriedText(text: string): InlineSegment {
|
||||
function textDirectiveSegment(text: string): InlineSegment {
|
||||
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)
|
||||
const escaping: InlineEscaping = context.bracketed ? 'bracketed' : 'backslash'
|
||||
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> {
|
||||
@@ -300,11 +300,11 @@ function emitEmphasis(nodes: readonly AdfNode[], spelling: string, depth: number
|
||||
const inner = emitRun(nodes, depth + 1, range.first, context)
|
||||
if (!inner.ok) return inner
|
||||
if (inner.value.carry !== undefined) return inner
|
||||
const carried = carryStrippedWhitespace(inner.value.segments)
|
||||
const spelled = spellEdgeWhitespace(inner.value.segments)
|
||||
return success({
|
||||
segments: [
|
||||
{ emphasis: 'open', escaping: 'none', nodes: { ...range, depth }, text: spelling },
|
||||
...carried,
|
||||
...spelled,
|
||||
{ emphasis: 'close', escaping: 'none', nodes: { ...range, depth }, text: spelling },
|
||||
],
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AdfNode } from '../../adf/document.ts'
|
||||
import type { ConvertErrorPath } from '../../result.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 { tryPipeCell } from './inline-line.ts'
|
||||
|
||||
@@ -26,16 +26,16 @@ export function tryPipeTable(node: AdfNode, path: ConvertErrorPath, flavour: Fla
|
||||
function pipeRows(node: AdfNode): AdfNode[][] | undefined {
|
||||
const rows = nodeContent(node)
|
||||
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[][] = []
|
||||
for (const [index, row] of rows.entries()) {
|
||||
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 paragraphs: AdfNode[] = []
|
||||
for (const cell of cells) {
|
||||
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)
|
||||
}
|
||||
grid.push(paragraphs)
|
||||
@@ -46,6 +46,6 @@ function pipeRows(node: AdfNode): AdfNode[][] | undefined {
|
||||
function plainParagraph(cell: AdfNode): AdfNode | undefined {
|
||||
const content = nodeContent(cell)
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user