diff --git a/src/__tests__/highlightSpans.test.ts b/src/__tests__/highlightSpans.test.ts
index 207a8de..45823c9 100644
--- a/src/__tests__/highlightSpans.test.ts
+++ b/src/__tests__/highlightSpans.test.ts
@@ -1,50 +1,70 @@
import { describe, it, expect } from 'vitest';
-// Test the highlight-spans regex behaviour directly.
-// The bug (issue #2): a backtick span at the very start of a line was not
-// recognised because the pattern required a non-backtick character before the
-// opening backtick. The fix adds `^` as an alternative via the `m` flag.
-
+// Mirror the fixed pattern from slideView.ts highlightBlockSpans.
+// Uses a lookbehind so no preceding character is consumed, fixing:
+// issue #2 – spans at the start of a line were never matched
+// issue #3 – adjacent spans like `foo`(`bar`) incorrectly captured (
function applyHighlightPattern(html: string): string {
- // Mirrors the fixed pattern in highlightBlockSpans (highlightSpans === true).
- const pattern = /(^|[^`])`([^`]+?)`/gm;
- return html.replace(pattern, (m, e, c) => {
- if (e === '\\') return m.slice(1);
- return e + ``;
+ const pattern = /(? {
+ return ``;
});
}
-describe('highlightSpans regex (issue #2)', () => {
- it('highlights a span preceded by a non-backtick character', () => {
- const result = applyHighlightPattern('call `method`(arg)');
- expect(result).toContain('');
- });
+describe('highlightSpans regex', () => {
+ describe('basic highlighting', () => {
+ it('highlights a span preceded by a non-backtick character', () => {
+ const result = applyHighlightPattern('call `method`(arg)');
+ expect(result).toContain('');
+ expect(result).toContain('(arg)');
+ });
- it('highlights a span at the very start of a string', () => {
- const result = applyHighlightPattern('`highlightedMethod`(arg1, arg2)');
- expect(result).toContain('');
+ it('does not treat fenced code delimiters (```) as highlight spans', () => {
+ const result = applyHighlightPattern('```js\ncode\n```');
+ expect(result).not.toContain('remark-code-span-highlighted');
+ });
});
- it('highlights a span at the start of a new line', () => {
- const html = 'normalMethod(a)\n`highlightedMethod`(b)';
- const result = applyHighlightPattern(html);
- expect(result).toContain('');
- expect(result).toContain('normalMethod(a)');
- });
+ describe('issue #2 – span at start of line', () => {
+ it('highlights a span at the very start of a string', () => {
+ const result = applyHighlightPattern('`highlightedMethod`(arg1, arg2)');
+ expect(result).toContain('');
+ });
- it('does not double-highlight adjacent backtick spans', () => {
- const result = applyHighlightPattern('`a` and `b`');
- expect(result).toContain('');
- expect(result).toContain('');
+ it('highlights a span at the start of a new line', () => {
+ const html = 'normalMethod(a)\n`highlightedMethod`(b)';
+ const result = applyHighlightPattern(html);
+ expect(result).toContain('');
+ expect(result).toContain('normalMethod(a)');
+ });
});
- it('does not highlight a backtick span that is escaped', () => {
- const result = applyHighlightPattern('\\`notHighlighted`');
- expect(result).not.toContain('remark-code-span-highlighted');
+ describe('issue #3 – trailing parenthesis not captured', () => {
+ it('does not include ( after closing backtick in the highlighted span', () => {
+ const result = applyHighlightPattern('case `class Token`(value)');
+ expect(result).toContain('');
+ expect(result).toMatch(/<\/span>\(value\)/);
+ });
+
+ it('correctly highlights both spans in `foo`(`bar`)', () => {
+ const result = applyHighlightPattern('`foo`(`bar`)');
+ expect(result).toContain('');
+ expect(result).toContain('');
+ // ( must not be inside either span
+ expect(result).not.toContain('remark-code-span-highlighted">(');
+ expect(result).not.toContain('(');
+ });
+
+ it('does not capture ( when it follows the closing backtick with spaces', () => {
+ const result = applyHighlightPattern('`method` (arg)');
+ expect(result).toMatch(/<\/span> \(arg\)/);
+ });
});
- it('does not treat fenced code delimiters (```) as highlight spans', () => {
- const result = applyHighlightPattern('```js\ncode\n```');
- expect(result).not.toContain('remark-code-span-highlighted');
+ describe('escape handling', () => {
+ it('does not highlight a backtick span preceded by a backslash', () => {
+ const result = applyHighlightPattern('\\`notHighlighted`');
+ expect(result).not.toContain('remark-code-span-highlighted');
+ });
});
});
diff --git a/src/mdeck/views/slideView.ts b/src/mdeck/views/slideView.ts
index a28fc34..02bb3f9 100644
--- a/src/mdeck/views/slideView.ts
+++ b/src/mdeck/views/slideView.ts
@@ -211,18 +211,24 @@ function highlightBlockLines(block: HTMLElement, lines: number[]): void {
function highlightBlockSpans(block: HTMLElement, highlightSpans: boolean | RegExp): void {
let pattern: RegExp;
if (highlightSpans === true) {
- pattern = /(^|[^`])`([^`]+?)`/gm;
+ // Use a lookbehind so the opening backtick is not consumed along with its
+ // preceding character. This fixes two bugs:
+ // - #2: spans at the start of a line were never matched (no preceding char)
+ // - #3: adjacent spans like `foo`(`bar`) incorrectly captured ( as content
+ // because the old ([^`]) group consumed the last char of the first span,
+ // treating its closing backtick as the opening of a new span.
+ // The lookbehind also handles escape: \` is not treated as an opening backtick.
+ pattern = /(? {
if (node instanceof HTMLElement) {
- node.innerHTML = node.innerHTML.replace(pattern, (m, e, c) => {
- if (e === '\\') return m.slice(1);
- return e + ``;
+ node.innerHTML = node.innerHTML.replace(pattern, (_m, c) => {
+ return ``;
});
}
});