@@ -294,13 +294,7 @@ function maskUrlLiterals(value: string) {
294294 const referenceDefinitionMatch = line . match ( / ^ ( \s { 0 , 3 } \[ [ ^ \] ] + \] : \s * ) ( \S + ) ( .* ) $ / ) ;
295295 if ( referenceDefinitionMatch ) {
296296 const prefix = referenceDefinitionMatch [ 1 ] ?? "" ;
297- const rawDestination = referenceDefinitionMatch [ 2 ] ?? "" ;
298- const suffix = referenceDefinitionMatch [ 3 ] ?? "" ;
299- if ( rawDestination . startsWith ( "<" ) && rawDestination . endsWith ( ">" ) ) {
300- const innerDestination = rawDestination . slice ( 1 , - 1 ) ;
301- return `${ prefix } <${ toPlaceholder ( innerDestination ) } >${ suffix } ` ;
302- }
303- return `${ prefix } ${ toPlaceholder ( rawDestination ) } ${ suffix } ` ;
297+ return `${ prefix } ${ toPlaceholder ( line . slice ( prefix . length ) ) } ` ;
304298 }
305299
306300 const withAutolinksMasked = line . replace (
@@ -377,6 +371,174 @@ function replaceInlineBackslashDelimiters(value: string) {
377371 return output ;
378372}
379373
374+ function appendDisplayMathBlock ( output : string , body : string ) {
375+ let next = output . replace ( / [ \t ] + $ / , "" ) ;
376+ if ( next . length > 0 && ! next . endsWith ( "\n" ) ) {
377+ next += "\n\n" ;
378+ }
379+ next += `$$\n${ body } \n$$` ;
380+ return next ;
381+ }
382+
383+ function replaceSingleLineBackslashDisplayDelimiters ( value : string ) {
384+ let output = "" ;
385+ let cursor = 0 ;
386+ let index = 0 ;
387+
388+ while ( index < value . length ) {
389+ if (
390+ value [ index ] === "\\" &&
391+ index + 1 < value . length &&
392+ value [ index + 1 ] === "[" &&
393+ ! isEscaped ( value , index )
394+ ) {
395+ const start = index ;
396+ let closeIndex = - 1 ;
397+ let scan = index + 2 ;
398+ while ( scan < value . length ) {
399+ if ( value [ scan ] === "\n" || value [ scan ] === "\r" ) {
400+ break ;
401+ }
402+ if (
403+ value [ scan ] === "\\" &&
404+ scan + 1 < value . length &&
405+ value [ scan + 1 ] === "]" &&
406+ ! isEscaped ( value , scan )
407+ ) {
408+ closeIndex = scan ;
409+ break ;
410+ }
411+ scan += 1 ;
412+ }
413+
414+ if ( closeIndex >= 0 ) {
415+ const body = value . slice ( start + 2 , closeIndex ) . trim ( ) ;
416+ if ( body . length > 0 ) {
417+ output = appendDisplayMathBlock ( output + value . slice ( cursor , start ) , body ) ;
418+ const nextIndex = closeIndex + 2 ;
419+ if ( nextIndex < value . length && value [ nextIndex ] !== "\n" && value [ nextIndex ] !== "\r" ) {
420+ output += "\n\n" ;
421+ }
422+ index = nextIndex ;
423+ cursor = nextIndex ;
424+ continue ;
425+ }
426+ }
427+ }
428+
429+ index += 1 ;
430+ }
431+
432+ output += value . slice ( cursor ) ;
433+ return output ;
434+ }
435+
436+ function isSingleDollar ( value : string , index : number ) {
437+ return (
438+ value [ index ] === "$" &&
439+ ! isEscaped ( value , index ) &&
440+ value [ index - 1 ] !== "$" &&
441+ value [ index + 1 ] !== "$"
442+ ) ;
443+ }
444+
445+ function isValidSingleDollarOpener ( value : string , index : number ) {
446+ const next = value [ index + 1 ] ;
447+ return Boolean ( next ) && ! / \s / . test ( next ) ;
448+ }
449+
450+ function isValidSingleDollarCloser ( value : string , index : number ) {
451+ const previous = value [ index - 1 ] ;
452+ const next = value [ index + 1 ] ;
453+ return Boolean ( previous ) && ! / \s / . test ( previous ) && ! / [ 0 - 9 ] / . test ( next ?? "" ) ;
454+ }
455+
456+ function collectDoubleDollarMathRanges ( value : string ) {
457+ const ranges : Array < [ number , number ] > = [ ] ;
458+ let index = 0 ;
459+
460+ while ( index < value . length ) {
461+ if (
462+ value [ index ] !== "$" ||
463+ value [ index + 1 ] !== "$" ||
464+ isEscaped ( value , index )
465+ ) {
466+ index += 1 ;
467+ continue ;
468+ }
469+
470+ const start = index ;
471+ let closeIndex = index + 2 ;
472+ while ( closeIndex < value . length ) {
473+ if (
474+ value [ closeIndex ] === "$" &&
475+ value [ closeIndex + 1 ] === "$" &&
476+ ! isEscaped ( value , closeIndex )
477+ ) {
478+ ranges . push ( [ start , closeIndex + 2 ] ) ;
479+ index = closeIndex + 2 ;
480+ break ;
481+ }
482+ closeIndex += 1 ;
483+ }
484+
485+ if ( closeIndex >= value . length ) {
486+ index += 2 ;
487+ }
488+ }
489+
490+ return ranges ;
491+ }
492+
493+ function escapeInvalidSingleDollarDelimiters ( value : string ) {
494+ const protectedRanges = collectDoubleDollarMathRanges ( value ) ;
495+ const isProtected = ( position : number ) =>
496+ protectedRanges . some ( ( [ start , end ] ) => position >= start && position < end ) ;
497+ const isGuardableSingleDollar = ( position : number ) =>
498+ ! isProtected ( position ) && isSingleDollar ( value , position ) ;
499+ const validDelimiterIndexes = new Set < number > ( ) ;
500+ let index = 0 ;
501+
502+ while ( index < value . length ) {
503+ if ( ! isGuardableSingleDollar ( index ) || ! isValidSingleDollarOpener ( value , index ) ) {
504+ index += 1 ;
505+ continue ;
506+ }
507+
508+ let closeIndex = index + 1 ;
509+ let foundClose = false ;
510+ while ( closeIndex < value . length ) {
511+ if ( value [ closeIndex ] === "\n" || value [ closeIndex ] === "\r" ) {
512+ break ;
513+ }
514+ if ( isGuardableSingleDollar ( closeIndex ) ) {
515+ if ( isValidSingleDollarCloser ( value , closeIndex ) ) {
516+ validDelimiterIndexes . add ( index ) ;
517+ validDelimiterIndexes . add ( closeIndex ) ;
518+ index = closeIndex + 1 ;
519+ foundClose = true ;
520+ }
521+ break ;
522+ }
523+ closeIndex += 1 ;
524+ }
525+
526+ if ( ! foundClose ) {
527+ index += 1 ;
528+ }
529+ }
530+
531+ let output = "" ;
532+ for ( let cursor = 0 ; cursor < value . length ; cursor += 1 ) {
533+ if ( isGuardableSingleDollar ( cursor ) && ! validDelimiterIndexes . has ( cursor ) ) {
534+ output += "\\$" ;
535+ } else {
536+ output += value [ cursor ] ;
537+ }
538+ }
539+ return output ;
540+ }
541+
380542function convertBackslashBlockDelimiters ( value : string ) {
381543 const lines = value . split ( / \r ? \n / ) ;
382544 const output : string [ ] = [ ] ;
@@ -460,8 +622,10 @@ function normalizeBackslashMathDelimitersInChunk(value: string) {
460622 const { masked : linkMasked , restore : restoreLinks } = maskMarkdownLinkDestinations ( inlineCodeMasked ) ;
461623 const { masked : urlMasked , restore : restoreUrls } = maskUrlLiterals ( linkMasked ) ;
462624 const withBlockMath = convertBackslashBlockDelimiters ( urlMasked ) ;
463- const withInlineMath = replaceInlineBackslashDelimiters ( withBlockMath ) ;
464- return restoreInlineCode ( restoreLinks ( restoreUrls ( withInlineMath ) ) ) ;
625+ const withSingleLineDisplayMath = replaceSingleLineBackslashDisplayDelimiters ( withBlockMath ) ;
626+ const withInlineMath = replaceInlineBackslashDelimiters ( withSingleLineDisplayMath ) ;
627+ const withDollarGuard = escapeInvalidSingleDollarDelimiters ( withInlineMath ) ;
628+ return restoreInlineCode ( restoreLinks ( restoreUrls ( withDollarGuard ) ) ) ;
465629}
466630
467631export function normalizeBackslashMathDelimiters ( value : string ) {
0 commit comments