Skip to content

Commit 944cb2a

Browse files
authored
Improve Katex copy robustness (#274194)
1 parent 411f0c8 commit 944cb2a

9 files changed

+23
-16
lines changed

src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { CHAT_CATEGORY, stringifyItem } from './chatActions.js';
1212
import { ChatTreeItem, IChatWidgetService } from '../chat.js';
1313
import { ChatContextKeys } from '../../common/chatContextKeys.js';
1414
import { IChatRequestViewModel, IChatResponseViewModel, isChatTreeItem, isRequestVM, isResponseVM } from '../../common/chatViewModel.js';
15+
import { katexContainerClassName, katexContainerLatexAttributeName } from '../../../markdown/common/markedKatexExtension.js';
1516

1617
export function registerChatCopyActions() {
1718
registerAction2(class CopyAllAction extends Action2 {
@@ -135,15 +136,13 @@ export function registerChatCopyActions() {
135136
// Otherwise, fallback to querying from the active element
136137
if (!selectedElement) {
137138
// eslint-disable-next-line no-restricted-syntax
138-
selectedElement = activeElement?.querySelector('.katex') ?? null;
139+
selectedElement = activeElement?.querySelector(`.${katexContainerClassName}`) ?? null;
139140
}
140141

141142
// Extract the LaTeX source from the annotation element
142-
const katexElement = dom.isHTMLElement(selectedElement) ? selectedElement.closest('.katex') : null;
143-
// eslint-disable-next-line no-restricted-syntax
144-
const annotation = katexElement?.querySelector('annotation[encoding="application/x-tex"]');
145-
if (annotation) {
146-
const latexSource = annotation.textContent || '';
143+
const katexElement = dom.isHTMLElement(selectedElement) ? selectedElement.closest(`.${katexContainerClassName}`) : null;
144+
const latexSource = katexElement?.getAttribute(katexContainerLatexAttributeName) || '';
145+
if (latexSource) {
147146
await clipboardService.writeText(latexSource);
148147
}
149148
}

src/vs/workbench/contrib/chat/browser/chatWidget.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ import { ChatViewPane } from './chatViewPane.js';
9595
import { ChatViewWelcomePart, IChatSuggestedPrompts, IChatViewWelcomeContent } from './viewsWelcome/chatViewWelcomeController.js';
9696
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
9797
import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js';
98+
import { katexContainerClassName } from '../../markdown/common/markedKatexExtension.js';
9899

99100
const $ = dom.$;
100101

@@ -2015,7 +2016,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
20152016

20162017
// Check if the context menu was opened on a KaTeX element
20172018
const target = e.browserEvent.target as HTMLElement;
2018-
const isKatexElement = target.closest('.katex') !== null;
2019+
const isKatexElement = target.closest(`.${katexContainerClassName}`) !== null;
20192020

20202021
const scopedContextKeyService = this.contextKeyService.createOverlay([
20212022
[ChatContextKeys.responseIsFiltered.key, isResponseVM(selected) && !!selected.errorDetails?.responseIsFiltered],

src/vs/workbench/contrib/markdown/browser/markedKatexSupport.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { MarkdownSanitizerConfig } from '../../../../base/browser/markdownRender
99
import { CodeWindow } from '../../../../base/browser/window.js';
1010
import { Lazy } from '../../../../base/common/lazy.js';
1111
import type * as marked from '../../../../base/common/marked/marked.js';
12-
import { MarkedKatexExtension } from '../common/markedKatexExtension.js';
12+
import { katexContainerLatexAttributeName, MarkedKatexExtension } from '../common/markedKatexExtension.js';
1313

1414
export class MarkedKatexSupport {
1515

@@ -32,6 +32,7 @@ export class MarkedKatexSupport {
3232
'stretchy',
3333
'encoding',
3434
'accent',
35+
katexContainerLatexAttributeName,
3536

3637
// SVG
3738
'd',

src/vs/workbench/contrib/markdown/common/markedKatexExtension.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55
import type * as marked from '../../../../base/common/marked/marked.js';
6+
import { htmlAttributeEncodeValue } from '../../../../base/common/strings.js';
67

78
export const mathInlineRegExp = /(?<![a-zA-Z0-9])(?<dollars>\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\k<dollars>(?![a-zA-Z0-9])/; // Non-standard, but ensure opening $ is not preceded and closing $ is not followed by word/number characters
8-
9+
export const katexContainerClassName = 'vscode-katex-container';
10+
export const katexContainerLatexAttributeName = 'data-latex';
911

1012
const inlineRule = new RegExp('^' + mathInlineRegExp.source);
1113

@@ -32,11 +34,15 @@ export namespace MarkedKatexExtension {
3234
return (token: marked.Tokens.Generic) => {
3335
let out: string;
3436
try {
35-
out = katex.renderToString(token.text, {
37+
const html = katex.renderToString(token.text, {
3638
...options,
3739
throwOnError: true,
3840
displayMode: token.displayMode,
3941
});
42+
43+
// Wrap in a container with attribute as a fallback for extracting the original LaTeX source
44+
// This ensures we can always retrieve the source even if the annotation element is not present
45+
out = `<span class="${katexContainerClassName}" ${katexContainerLatexAttributeName}="${htmlAttributeEncodeValue(token.text)}">${html}</span>`;
4046
} catch {
4147
// On failure, just use the original text including the wrapping $ or $$
4248
out = token.raw;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<p>Hello <span class="katex"><span class="katex-mathml"><math><semantics><mrow><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{2}</annotation></semantics></math></span><span class="katex-html"><span class="base"><span class="strut" style="height: 1.1901em; vertical-align: -0.345em"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.8451em"><span style="top: -2.655em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top: -3.23em"><span class="pstrut" style="height: 3em"></span><span class="frac-line" style="border-bottom-width: 0.04em"></span></span><span style="top: -3.394em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.345em"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span> World!</p>
1+
<p>Hello <span class="vscode-katex-container" data-latex="\frac{1}{2}"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{2}</annotation></semantics></math></span><span class="katex-html"><span class="base"><span class="strut" style="height: 1.1901em; vertical-align: -0.345em"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.8451em"><span style="top: -2.655em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top: -3.23em"><span class="pstrut" style="height: 3em"></span><span class="frac-line" style="border-bottom-width: 0.04em"></span></span><span style="top: -3.394em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.345em"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span> World!</p>
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<p><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{2}</annotation></semantics></math></span><span class="katex-html"><span class="base"><span class="strut" style="height: 1.1901em; vertical-align: -0.345em"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.8451em"><span style="top: -2.655em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top: -3.23em"><span class="pstrut" style="height: 3em"></span><span class="frac-line" style="border-bottom-width: 0.04em"></span></span><span style="top: -3.394em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.345em"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span> at start, and at end <span class="katex"><span class="katex-mathml"><math><semantics><mrow><msup><mi>x</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">x^2</annotation></semantics></math></span><span class="katex-html"><span class="base"><span class="strut" style="height: 0.8141em"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height: 0.8141em"><span style="top: -3.063em; margin-right: 0.05em"><span class="pstrut" style="height: 2.7em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></p>
1+
<p><span class="vscode-katex-container" data-latex="\frac{1}{2}"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{2}</annotation></semantics></math></span><span class="katex-html"><span class="base"><span class="strut" style="height: 1.1901em; vertical-align: -0.345em"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.8451em"><span style="top: -2.655em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top: -3.23em"><span class="pstrut" style="height: 3em"></span><span class="frac-line" style="border-bottom-width: 0.04em"></span></span><span style="top: -3.394em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.345em"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span> at start, and at end <span class="vscode-katex-container" data-latex="x^2"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><msup><mi>x</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">x^2</annotation></semantics></math></span><span class="katex-html"><span class="base"><span class="strut" style="height: 0.8141em"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height: 0.8141em"><span style="top: -3.063em; margin-right: 0.05em"><span class="pstrut" style="height: 2.7em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span></p>
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<p>Hello (<span class="katex"><span class="katex-mathml"><math><semantics><mrow><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{2}</annotation></semantics></math></span><span class="katex-html"><span class="base"><span class="strut" style="height: 1.1901em; vertical-align: -0.345em"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.8451em"><span style="top: -2.655em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top: -3.23em"><span class="pstrut" style="height: 3em"></span><span class="frac-line" style="border-bottom-width: 0.04em"></span></span><span style="top: -3.394em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.345em"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>) and [<span class="katex"><span class="katex-mathml"><math><semantics><mrow><msup><mi>x</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">x^2</annotation></semantics></math></span><span class="katex-html"><span class="base"><span class="strut" style="height: 0.8141em"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height: 0.8141em"><span style="top: -3.063em; margin-right: 0.05em"><span class="pstrut" style="height: 2.7em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span>] work fine</p>
1+
<p>Hello (<span class="vscode-katex-container" data-latex="\frac{1}{2}"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{2}</annotation></semantics></math></span><span class="katex-html"><span class="base"><span class="strut" style="height: 1.1901em; vertical-align: -0.345em"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.8451em"><span style="top: -2.655em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top: -3.23em"><span class="pstrut" style="height: 3em"></span><span class="frac-line" style="border-bottom-width: 0.04em"></span></span><span style="top: -3.394em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.345em"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>) and [<span class="vscode-katex-container" data-latex="x^2"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><msup><mi>x</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">x^2</annotation></semantics></math></span><span class="katex-html"><span class="base"><span class="strut" style="height: 0.8141em"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height: 0.8141em"><span style="top: -3.063em; margin-right: 0.05em"><span class="pstrut" style="height: 2.7em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span>] work fine</p>

0 commit comments

Comments
 (0)