@@ -235,7 +235,10 @@ function parseInlineRecursive(text: string, stop?: string): [Token[], number] {
235235
236236 while ( i < text . length ) {
237237 if ( stop && text . startsWith ( stop , i ) ) {
238- return [ tokens , i ]
238+ // if we're closing "_", only do so when it's a real closing‐underscore
239+ if ( stop !== '_' || isClosingUnderscore ( text , i ) ) {
240+ return [ tokens , i ]
241+ }
239242 }
240243
241244 // Image: 
@@ -336,8 +339,8 @@ function parseInlineRecursive(text: string, stop?: string): [Token[], number] {
336339 continue
337340 }
338341
339- // Italic (* or _)
340- if ( text [ i ] === '*' || text [ i ] === '_' ) {
342+ // Italic opener: "*" always, "_" only when isOpeningUnderscore
343+ if ( text [ i ] === '*' || text [ i ] === '_' && isOpeningUnderscore ( text , i ) ) {
341344 const delimiter = text [ i ]
342345 // For '*' only: if surrounding non-space chars are digits, treat as literal
343346 if ( delimiter === '*' ) {
@@ -361,11 +364,21 @@ function parseInlineRecursive(text: string, stop?: string): [Token[], number] {
361364 continue
362365 }
363366 }
364- i ++
365- const [ innerTokens , consumed ] = parseInlineRecursive ( text . slice ( i ) , delimiter )
366- i += consumed
367- i ++ // skip closing delimiter
368- tokens . push ( { type : 'italic' , children : innerTokens } )
367+
368+ // look ahead for the rest of the text
369+ const rest = text . slice ( i + 1 )
370+ const [ innerTokens , consumed ] = parseInlineRecursive ( rest , delimiter )
371+
372+ if ( consumed < rest . length ) {
373+ // we found a real closing delimiter
374+ tokens . push ( { type : 'italic' , children : innerTokens } )
375+ // skip open, inner content, and closing
376+ i += 1 + consumed + 1
377+ } else if ( delimiter ) {
378+ // no closing delimiter — just emit a literal underscore/star
379+ tokens . push ( { type : 'text' , content : delimiter } )
380+ i += 1
381+ }
369382 continue
370383 }
371384
@@ -376,10 +389,12 @@ function parseInlineRecursive(text: string, stop?: string): [Token[], number] {
376389 && text [ j ] !== '`'
377390 && ! ( text . startsWith ( '**' , j ) || text . startsWith ( '__' , j ) )
378391 && text [ j ] !== '*'
379- && text [ j ] !== '_'
392+ && ! ( text [ j ] === '_' && isOpeningUnderscore ( text , j ) )
380393 && text [ j ] !== '['
381- // && text[j] !== '!'
382- && ! ( stop && text . startsWith ( stop , j ) )
394+ // only break on stop when it's a real delimiter
395+ && ! ( stop
396+ && text . startsWith ( stop , j )
397+ && ( stop !== '_' || isClosingUnderscore ( text , j ) ) )
383398 // handle  for images but not for `text!`
384399 && ! ( text [ j ] === '!' && j + 1 < text . length && text [ j + 1 ] === '[' )
385400 ) {
@@ -392,6 +407,19 @@ function parseInlineRecursive(text: string, stop?: string): [Token[], number] {
392407 return [ tokens , i ]
393408}
394409
410+ function isOpeningUnderscore ( text : string , pos : number ) : boolean {
411+ const prev = text [ pos - 1 ] ?? '\n'
412+ const next = text [ pos + 1 ] ?? '\n'
413+ // can open only if next isn't whitespace, and prev isn't alnum
414+ return ! / \s / . test ( next ) && ! / \w / . test ( prev )
415+ }
416+ function isClosingUnderscore ( text : string , pos : number ) : boolean {
417+ const prev = text [ pos - 1 ] ?? '\n'
418+ const next = text [ pos + 1 ] ?? '\n'
419+ // can close only if prev isn't whitespace, and next isn't alnum
420+ return ! / \s / . test ( prev ) && ! / \w / . test ( next )
421+ }
422+
395423function renderTokens ( tokens : Token [ ] , keyPrefix = '' ) : ReactNode [ ] {
396424 return tokens . map ( ( token , index ) => {
397425 const key = `${ keyPrefix } ${ index } `
0 commit comments