Skip to content

Commit

Permalink
Add support for output position tracking
Browse files Browse the repository at this point in the history
This adds support for tracking line and column numbers (not offsets)
when generating output.
This information is particularly useful to print information based
on a future line wrap.

Related to: syntax-tree/mdast-util-mdx-jsx#3.
  • Loading branch information
wooorm committed Jan 16, 2022
1 parent 407af18 commit 52c18b4
Show file tree
Hide file tree
Showing 20 changed files with 487 additions and 164 deletions.
11 changes: 9 additions & 2 deletions lib/handle/blockquote.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,21 @@

import {containerFlow} from '../util/container-flow.js'
import {indentLines} from '../util/indent-lines.js'
import {track} from '../util/track.js'

/**
* @type {Handle}
* @param {Blockquote} node
*/
export function blockquote(node, _, context) {
export function blockquote(node, _, context, safeOptions) {
const exit = context.enter('blockquote')
const value = indentLines(containerFlow(node, context), map)
const tracker = track(safeOptions)
tracker.move('> ')
tracker.shift(2)
const value = indentLines(
containerFlow(node, context, tracker.current()),
map
)
exit()
return value
}
Expand Down
76 changes: 39 additions & 37 deletions lib/handle/code.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,61 +10,63 @@ import {formatCodeAsIndented} from '../util/format-code-as-indented.js'
import {checkFence} from '../util/check-fence.js'
import {indentLines} from '../util/indent-lines.js'
import {safe} from '../util/safe.js'
import {track} from '../util/track.js'

/**
* @type {Handle}
* @param {Code} node
*/
export function code(node, _, context) {
export function code(node, _, context, safeOptions) {
const marker = checkFence(context)
const raw = node.value || ''
const suffix = marker === '`' ? 'GraveAccent' : 'Tilde'
/** @type {string} */
let value
/** @type {Exit} */
let exit

if (formatCodeAsIndented(node, context)) {
exit = context.enter('codeIndented')
value = indentLines(raw, map)
} else {
const sequence = marker.repeat(Math.max(longestStreak(raw, marker) + 1, 3))
/** @type {Exit} */
let subexit
exit = context.enter('codeFenced')
value = sequence
const exit = context.enter('codeIndented')
const value = indentLines(raw, map)
exit()
return value
}

const tracker = track(safeOptions)
const sequence = marker.repeat(Math.max(longestStreak(raw, marker) + 1, 3))
const exit = context.enter('codeFenced')
let value = tracker.move(sequence)

if (node.lang) {
subexit = context.enter('codeFencedLang' + suffix)
value += safe(context, node.lang, {
before: '`',
if (node.lang) {
const subexit = context.enter('codeFencedLang' + suffix)
value += tracker.move(
safe(context, node.lang, {
before: value,
after: ' ',
encode: ['`']
encode: ['`'],
...tracker.current()
})
subexit()
}

if (node.lang && node.meta) {
subexit = context.enter('codeFencedMeta' + suffix)
value +=
' ' +
safe(context, node.meta, {
before: ' ',
after: '\n',
encode: ['`']
})
subexit()
}
)
subexit()
}

value += '\n'
if (node.lang && node.meta) {
const subexit = context.enter('codeFencedMeta' + suffix)
value += tracker.move(' ')
value += tracker.move(
safe(context, node.meta, {
before: value,
after: '\n',
encode: ['`'],
...tracker.current()
})
)
subexit()
}

if (raw) {
value += raw + '\n'
}
value += tracker.move('\n')

value += sequence
if (raw) {
value += tracker.move(raw + '\n')
}

value += tracker.move(sequence)
exit()
return value
}
Expand Down
47 changes: 35 additions & 12 deletions lib/handle/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,27 @@
import {association} from '../util/association.js'
import {checkQuote} from '../util/check-quote.js'
import {safe} from '../util/safe.js'
import {track} from '../util/track.js'

/**
* @type {Handle}
* @param {Definition} node
*/
export function definition(node, _, context) {
const marker = checkQuote(context)
const suffix = marker === '"' ? 'Quote' : 'Apostrophe'
export function definition(node, _, context, safeOptions) {
const quote = checkQuote(context)
const suffix = quote === '"' ? 'Quote' : 'Apostrophe'
const exit = context.enter('definition')
let subexit = context.enter('label')
let value =
'[' + safe(context, association(node), {before: '[', after: ']'}) + ']: '
const tracker = track(safeOptions)
let value = tracker.move('[')
value += tracker.move(
safe(context, association(node), {
before: value,
after: ']',
...tracker.current()
})
)
value += tracker.move(']: ')

subexit()

Expand All @@ -28,22 +37,36 @@ export function definition(node, _, context) {
/[\0- \u007F]/.test(node.url)
) {
subexit = context.enter('destinationLiteral')
value += '<' + safe(context, node.url, {before: '<', after: '>'}) + '>'
value += tracker.move('<')
value += tracker.move(
safe(context, node.url, {before: value, after: '>', ...tracker.current()})
)
value += tracker.move('>')
} else {
// No whitespace, raw is prettier.
subexit = context.enter('destinationRaw')
value += safe(context, node.url, {before: ' ', after: ' '})
value += tracker.move(
safe(context, node.url, {
before: value,
after: node.title ? ' ' : '\n',
...tracker.current()
})
)
}

subexit()

if (node.title) {
subexit = context.enter('title' + suffix)
value +=
' ' +
marker +
safe(context, node.title, {before: marker, after: marker}) +
marker
value += tracker.move(' ' + quote)
value += tracker.move(
safe(context, node.title, {
before: value,
after: quote,
...tracker.current()
})
)
value += tracker.move(quote)
subexit()
}

Expand Down
19 changes: 13 additions & 6 deletions lib/handle/emphasis.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import {checkEmphasis} from '../util/check-emphasis.js'
import {containerPhrasing} from '../util/container-phrasing.js'
import {track} from '../util/track.js'

emphasis.peek = emphasisPeek

Expand All @@ -16,15 +17,21 @@ emphasis.peek = emphasisPeek
* @type {Handle}
* @param {Emphasis} node
*/
export function emphasis(node, _, context) {
export function emphasis(node, _, context, safeOptions) {
const marker = checkEmphasis(context)
const exit = context.enter('emphasis')
const value = containerPhrasing(node, context, {
before: marker,
after: marker
})
const tracker = track(safeOptions)
let value = tracker.move(marker)
value += tracker.move(
containerPhrasing(node, context, {
before: value,
after: marker,
...tracker.current()
})
)
value += tracker.move(marker)
exit()
return marker + value + marker
return value
}

/**
Expand Down
24 changes: 21 additions & 3 deletions lib/handle/heading.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,24 @@

import {formatHeadingAsSetext} from '../util/format-heading-as-setext.js'
import {containerPhrasing} from '../util/container-phrasing.js'
import {track} from '../util/track.js'

/**
* @type {Handle}
* @param {Heading} node
*/
export function heading(node, _, context) {
export function heading(node, _, context, safeOptions) {
const rank = Math.max(Math.min(6, node.depth || 1), 1)
const tracker = track(safeOptions)

if (formatHeadingAsSetext(node, context)) {
const exit = context.enter('headingSetext')
const subexit = context.enter('phrasing')
const value = containerPhrasing(node, context, {before: '\n', after: '\n'})
const value = containerPhrasing(node, context, {
...tracker.current(),
before: '\n',
after: '\n'
})
subexit()
exit()

Expand All @@ -37,9 +43,21 @@ export function heading(node, _, context) {
const sequence = '#'.repeat(rank)
const exit = context.enter('headingAtx')
const subexit = context.enter('phrasing')
let value = containerPhrasing(node, context, {before: '# ', after: '\n'})

// Note: for proper tracking, we should reset the output positions when there
// is no content returned, because then the space is not output.
// Practically, in that case, there is no content, so it doesn’t matter that
// we’ve tracked one too many characters.
tracker.move(sequence + ' ')

let value = containerPhrasing(node, context, {
before: '# ',
after: '\n',
...tracker.current()
})

if (/^[\t ]/.test(value)) {
// To do: what effect has the character reference on tracking?
value =
'&#x' +
value.charCodeAt(0).toString(16).toUpperCase() +
Expand Down
32 changes: 25 additions & 7 deletions lib/handle/image-reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,52 @@

import {association} from '../util/association.js'
import {safe} from '../util/safe.js'
import {track} from '../util/track.js'

imageReference.peek = imageReferencePeek

/**
* @type {Handle}
* @param {ImageReference} node
*/
export function imageReference(node, _, context) {
export function imageReference(node, _, context, safeOptions) {
const type = node.referenceType
const exit = context.enter('imageReference')
let subexit = context.enter('label')
const alt = safe(context, node.alt, {before: '[', after: ']'})
let value = '![' + alt + ']'
const tracker = track(safeOptions)
let value = tracker.move('![')
const alt = safe(context, node.alt, {
before: value,
after: ']',
...tracker.current()
})
value += tracker.move(alt + '][')

subexit()
// Hide the fact that we’re in phrasing, because escapes don’t work.
const stack = context.stack
context.stack = []
subexit = context.enter('reference')
const reference = safe(context, association(node), {before: '[', after: ']'})
// Note: for proper tracking, we should reset the output positions when we end
// up making a `shortcut` reference, because then there is no brace output.
// Practically, in that case, there is no content, so it doesn’t matter that
// we’ve tracked one too many characters.
const reference = safe(context, association(node), {
before: value,
after: ']',
...tracker.current()
})
subexit()
context.stack = stack
exit()

if (type === 'full' || !alt || alt !== reference) {
value += '[' + reference + ']'
} else if (type !== 'shortcut') {
value += '[]'
value += tracker.move(reference + ']')
} else if (type === 'shortcut') {
// Remove the unwanted `[`.
value = value.slice(0, -1)
} else {
value += tracker.move(']')
}

return value
Expand Down

0 comments on commit 52c18b4

Please sign in to comment.