Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts
5263 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as dom from '../../../../../base/browser/dom.js';
7
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
8
import { localize2 } from '../../../../../nls.js';
9
import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
10
import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js';
11
import { katexContainerClassName, katexContainerLatexAttributeName } from '../../../markdown/common/markedKatexExtension.js';
12
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
13
import { IChatRequestViewModel, IChatResponseViewModel, isChatTreeItem, isRequestVM, isResponseVM } from '../../common/model/chatViewModel.js';
14
import { ChatTreeItem, IChatWidgetService } from '../chat.js';
15
import { CHAT_CATEGORY, stringifyItem } from './chatActions.js';
16
17
export function registerChatCopyActions() {
18
registerAction2(class CopyAllAction extends Action2 {
19
constructor() {
20
super({
21
id: 'workbench.action.chat.copyAll',
22
title: localize2('interactive.copyAll.label', "Copy All"),
23
f1: false,
24
category: CHAT_CATEGORY,
25
menu: {
26
id: MenuId.ChatContext,
27
when: ChatContextKeys.responseIsFiltered.negate(),
28
group: 'copy',
29
}
30
});
31
}
32
33
run(accessor: ServicesAccessor, context?: ChatTreeItem) {
34
const clipboardService = accessor.get(IClipboardService);
35
const chatWidgetService = accessor.get(IChatWidgetService);
36
const widget = ((isRequestVM(context) || isResponseVM(context)) && chatWidgetService.getWidgetBySessionResource(context.sessionResource)) || chatWidgetService.lastFocusedWidget;
37
if (widget) {
38
const viewModel = widget.viewModel;
39
const sessionAsText = viewModel?.getItems()
40
.filter((item): item is (IChatRequestViewModel | IChatResponseViewModel) => isRequestVM(item) || (isResponseVM(item) && !item.errorDetails?.responseIsFiltered))
41
.map(item => stringifyItem(item))
42
.join('\n\n');
43
if (sessionAsText) {
44
clipboardService.writeText(sessionAsText);
45
}
46
}
47
}
48
});
49
50
registerAction2(class CopyItemAction extends Action2 {
51
constructor() {
52
super({
53
id: 'workbench.action.chat.copyItem',
54
title: localize2('interactive.copyItem.label', "Copy"),
55
f1: false,
56
category: CHAT_CATEGORY,
57
menu: {
58
id: MenuId.ChatContext,
59
when: ChatContextKeys.responseIsFiltered.negate(),
60
group: 'copy',
61
}
62
});
63
}
64
65
async run(accessor: ServicesAccessor, ...args: unknown[]) {
66
const chatWidgetService = accessor.get(IChatWidgetService);
67
const clipboardService = accessor.get(IClipboardService);
68
69
const widget = chatWidgetService.lastFocusedWidget;
70
let item = args[0] as ChatTreeItem | undefined;
71
if (!isChatTreeItem(item)) {
72
item = widget?.getFocus();
73
if (!item) {
74
return;
75
}
76
}
77
78
// If there is a text selection, and focus is inside the widget, copy the selected text.
79
// Otherwise, context menu with no selection -> copy the full item
80
const nativeSelection = dom.getActiveWindow().getSelection();
81
const selectedText = nativeSelection?.toString();
82
if (widget && selectedText && selectedText.length > 0 && dom.isAncestor(dom.getActiveElement(), widget.domNode)) {
83
await clipboardService.writeText(selectedText);
84
return;
85
}
86
87
if (!isRequestVM(item) && !isResponseVM(item)) {
88
return;
89
}
90
91
const text = stringifyItem(item, false);
92
await clipboardService.writeText(text);
93
}
94
});
95
96
registerAction2(class CopyKatexMathSourceAction extends Action2 {
97
constructor() {
98
super({
99
id: 'workbench.action.chat.copyKatexMathSource',
100
title: localize2('chat.copyKatexMathSource.label', "Copy Math Source"),
101
f1: false,
102
category: CHAT_CATEGORY,
103
menu: {
104
id: MenuId.ChatContext,
105
group: 'copy',
106
when: ChatContextKeys.isKatexMathElement,
107
}
108
});
109
}
110
111
async run(accessor: ServicesAccessor, ...args: unknown[]) {
112
const chatWidgetService = accessor.get(IChatWidgetService);
113
const clipboardService = accessor.get(IClipboardService);
114
115
const widget = chatWidgetService.lastFocusedWidget;
116
let item = args[0] as ChatTreeItem | undefined;
117
if (!isChatTreeItem(item)) {
118
item = widget?.getFocus();
119
if (!item) {
120
return;
121
}
122
}
123
124
// Try to find a KaTeX element from the selection or active element
125
let selectedElement: Node | null = null;
126
127
// If there is a selection, and focus is inside the widget, extract the inner KaTeX element.
128
const activeElement = dom.getActiveElement();
129
const nativeSelection = dom.getActiveWindow().getSelection();
130
if (widget && nativeSelection && nativeSelection.rangeCount > 0 && dom.isAncestor(activeElement, widget.domNode)) {
131
const range = nativeSelection.getRangeAt(0);
132
selectedElement = range.commonAncestorContainer;
133
134
// If it's a text node, get its parent element
135
if (selectedElement.nodeType === Node.TEXT_NODE) {
136
selectedElement = selectedElement.parentElement;
137
}
138
}
139
140
// Otherwise, fallback to querying from the active element
141
if (!selectedElement) {
142
// eslint-disable-next-line no-restricted-syntax
143
selectedElement = activeElement?.querySelector(`.${katexContainerClassName}`) ?? null;
144
}
145
146
// Extract the LaTeX source from the annotation element
147
const katexElement = dom.isHTMLElement(selectedElement) ? selectedElement.closest(`.${katexContainerClassName}`) : null;
148
const latexSource = katexElement?.getAttribute(katexContainerLatexAttributeName) || '';
149
if (latexSource) {
150
await clipboardService.writeText(latexSource);
151
}
152
}
153
});
154
}
155
156