diff --git a/src/app/utils/mathParse.test.ts b/src/app/utils/mathParse.test.ts index 4824fe09a..3952a33b9 100644 --- a/src/app/utils/mathParse.test.ts +++ b/src/app/utils/mathParse.test.ts @@ -81,3 +81,18 @@ test('block and inline mixed with text', () => { { type: 'text', value: ' ok' }, ]); }); + +test('math inside a backtick code span stays literal text (#194 P4-4)', () => { + assert.deepEqual(splitMathSegments('inline `$y^2$` here'), [ + { type: 'text', value: 'inline `$y^2$` here' }, + ]); + assert.deepEqual(splitMathSegments('``$a$`` and $b$'), [ + { type: 'text', value: '``$a$`` and ' }, + { type: 'inline', value: 'b' }, + ]); + // An unclosed backtick is just a character. + assert.deepEqual(splitMathSegments('tick ` then $c$'), [ + { type: 'text', value: 'tick ` then ' }, + { type: 'inline', value: 'c' }, + ]); +}); diff --git a/src/app/utils/mathParse.ts b/src/app/utils/mathParse.ts index c228b3748..2b5bc8c51 100644 --- a/src/app/utils/mathParse.ts +++ b/src/app/utils/mathParse.ts @@ -88,6 +88,23 @@ export const splitMathSegments = (text: string): MathSegment[] => { }; while (i < text.length) { + // Backtick code spans are literal: `$x$` inside them must stay text so + // markdown's inline code wins (a run of N backticks closes with N). + if (text[i] === '`') { + let ticks = 0; + while (text[i + ticks] === '`') ticks += 1; + const fence = '`'.repeat(ticks); + const close = text.indexOf(fence, i + ticks); + if (close !== -1) { + buffer += text.slice(i, close + ticks); + i = close + ticks; + continue; + } + buffer += fence; + i += ticks; + continue; + } + // Escaped dollar: consume `\$` and emit a literal `$` as text. if (text[i] === '\\' && text[i + 1] === '$') { buffer += '$';