Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTreeContentPart.ts
3296 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 { IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js';
8
import { ITreeCompressionDelegate } from '../../../../../base/browser/ui/tree/asyncDataTree.js';
9
import { ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compressedObjectTreeModel.js';
10
import { ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js';
11
import { IAsyncDataSource, ITreeNode } from '../../../../../base/browser/ui/tree/tree.js';
12
import { Emitter, Event } from '../../../../../base/common/event.js';
13
import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';
14
import { localize } from '../../../../../nls.js';
15
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
16
import { FileKind, FileType } from '../../../../../platform/files/common/files.js';
17
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
18
import { WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js';
19
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
20
import { IThemeService } from '../../../../../platform/theme/common/themeService.js';
21
import { IResourceLabel, ResourceLabels } from '../../../../browser/labels.js';
22
import { ChatTreeItem } from '../chat.js';
23
import { IDisposableReference, ResourcePool } from './chatCollections.js';
24
import { IChatContentPart } from './chatContentParts.js';
25
import { IChatProgressRenderableResponseContent } from '../../common/chatModel.js';
26
import { IChatResponseProgressFileTreeData } from '../../common/chatService.js';
27
import { createFileIconThemableTreeContainerScope } from '../../../files/browser/views/explorerView.js';
28
import { IFilesConfiguration } from '../../../files/common/files.js';
29
30
const $ = dom.$;
31
32
export class ChatTreeContentPart extends Disposable implements IChatContentPart {
33
public readonly domNode: HTMLElement;
34
35
private readonly _onDidChangeHeight = this._register(new Emitter<void>());
36
public readonly onDidChangeHeight = this._onDidChangeHeight.event;
37
38
public readonly onDidFocus: Event<void>;
39
40
private tree: WorkbenchCompressibleAsyncDataTree<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData, void>;
41
42
constructor(
43
data: IChatResponseProgressFileTreeData,
44
element: ChatTreeItem,
45
treePool: TreePool,
46
treeDataIndex: number,
47
@IOpenerService private readonly openerService: IOpenerService
48
) {
49
super();
50
51
const ref = this._register(treePool.get());
52
this.tree = ref.object;
53
this.onDidFocus = this.tree.onDidFocus;
54
55
this._register(this.tree.onDidOpen((e) => {
56
if (e.element && !('children' in e.element)) {
57
this.openerService.open(e.element.uri);
58
}
59
}));
60
this._register(this.tree.onDidChangeCollapseState(() => {
61
this._onDidChangeHeight.fire();
62
}));
63
this._register(this.tree.onContextMenu((e) => {
64
e.browserEvent.preventDefault();
65
e.browserEvent.stopPropagation();
66
}));
67
68
this.tree.setInput(data).then(() => {
69
if (!ref.isStale()) {
70
this.tree.layout();
71
this._onDidChangeHeight.fire();
72
}
73
});
74
75
this.domNode = this.tree.getHTMLElement().parentElement!;
76
}
77
78
domFocus() {
79
this.tree.domFocus();
80
}
81
82
hasSameContent(other: IChatProgressRenderableResponseContent): boolean {
83
// No other change allowed for this content type
84
return other.kind === 'treeData';
85
}
86
87
addDisposable(disposable: IDisposable): void {
88
this._register(disposable);
89
}
90
}
91
92
export class TreePool extends Disposable {
93
private _pool: ResourcePool<WorkbenchCompressibleAsyncDataTree<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData, void>>;
94
95
public get inUse(): ReadonlySet<WorkbenchCompressibleAsyncDataTree<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData, void>> {
96
return this._pool.inUse;
97
}
98
99
constructor(
100
private _onDidChangeVisibility: Event<boolean>,
101
@IInstantiationService private readonly instantiationService: IInstantiationService,
102
@IConfigurationService private readonly configService: IConfigurationService,
103
@IThemeService private readonly themeService: IThemeService,
104
) {
105
super();
106
this._pool = this._register(new ResourcePool(() => this.treeFactory()));
107
}
108
109
private treeFactory(): WorkbenchCompressibleAsyncDataTree<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData, void> {
110
const resourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility }));
111
112
const container = $('.interactive-response-progress-tree');
113
this._register(createFileIconThemableTreeContainerScope(container, this.themeService));
114
115
const tree = this.instantiationService.createInstance(
116
WorkbenchCompressibleAsyncDataTree<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData>,
117
'ChatListRenderer',
118
container,
119
new ChatListTreeDelegate(),
120
new ChatListTreeCompressionDelegate(),
121
[new ChatListTreeRenderer(resourceLabels, this.configService.getValue('explorer.decorations'))],
122
new ChatListTreeDataSource(),
123
{
124
collapseByDefault: () => false,
125
expandOnlyOnTwistieClick: () => false,
126
identityProvider: {
127
getId: (e: IChatResponseProgressFileTreeData) => e.uri.toString()
128
},
129
accessibilityProvider: {
130
getAriaLabel: (element: IChatResponseProgressFileTreeData) => element.label,
131
getWidgetAriaLabel: () => localize('treeAriaLabel', "File Tree")
132
},
133
alwaysConsumeMouseWheel: false
134
});
135
136
return tree;
137
}
138
139
get(): IDisposableReference<WorkbenchCompressibleAsyncDataTree<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData, void>> {
140
const object = this._pool.get();
141
let stale = false;
142
return {
143
object,
144
isStale: () => stale,
145
dispose: () => {
146
stale = true;
147
this._pool.release(object);
148
}
149
};
150
}
151
}
152
153
class ChatListTreeDelegate implements IListVirtualDelegate<IChatResponseProgressFileTreeData> {
154
static readonly ITEM_HEIGHT = 22;
155
156
getHeight(element: IChatResponseProgressFileTreeData): number {
157
return ChatListTreeDelegate.ITEM_HEIGHT;
158
}
159
160
getTemplateId(element: IChatResponseProgressFileTreeData): string {
161
return 'chatListTreeTemplate';
162
}
163
}
164
165
class ChatListTreeCompressionDelegate implements ITreeCompressionDelegate<IChatResponseProgressFileTreeData> {
166
isIncompressible(element: IChatResponseProgressFileTreeData): boolean {
167
return !element.children;
168
}
169
}
170
171
interface IChatListTreeRendererTemplate {
172
templateDisposables: DisposableStore;
173
label: IResourceLabel;
174
}
175
176
class ChatListTreeRenderer implements ICompressibleTreeRenderer<IChatResponseProgressFileTreeData, void, IChatListTreeRendererTemplate> {
177
templateId: string = 'chatListTreeTemplate';
178
179
constructor(private labels: ResourceLabels, private decorations: IFilesConfiguration['explorer']['decorations']) { }
180
181
renderCompressedElements(element: ITreeNode<ICompressedTreeNode<IChatResponseProgressFileTreeData>, void>, index: number, templateData: IChatListTreeRendererTemplate): void {
182
templateData.label.element.style.display = 'flex';
183
const label = element.element.elements.map((e) => e.label);
184
templateData.label.setResource({ resource: element.element.elements[0].uri, name: label }, {
185
title: element.element.elements[0].label,
186
fileKind: element.children ? FileKind.FOLDER : FileKind.FILE,
187
extraClasses: ['explorer-item'],
188
fileDecorations: this.decorations
189
});
190
}
191
renderTemplate(container: HTMLElement): IChatListTreeRendererTemplate {
192
const templateDisposables = new DisposableStore();
193
const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true }));
194
return { templateDisposables, label };
195
}
196
renderElement(element: ITreeNode<IChatResponseProgressFileTreeData, void>, index: number, templateData: IChatListTreeRendererTemplate): void {
197
templateData.label.element.style.display = 'flex';
198
if (!element.children.length && element.element.type !== FileType.Directory) {
199
templateData.label.setFile(element.element.uri, {
200
fileKind: FileKind.FILE,
201
hidePath: true,
202
fileDecorations: this.decorations,
203
});
204
} else {
205
templateData.label.setResource({ resource: element.element.uri, name: element.element.label }, {
206
title: element.element.label,
207
fileKind: FileKind.FOLDER,
208
fileDecorations: this.decorations
209
});
210
}
211
}
212
disposeTemplate(templateData: IChatListTreeRendererTemplate): void {
213
templateData.templateDisposables.dispose();
214
}
215
}
216
217
class ChatListTreeDataSource implements IAsyncDataSource<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData> {
218
hasChildren(element: IChatResponseProgressFileTreeData): boolean {
219
return !!element.children;
220
}
221
222
async getChildren(element: IChatResponseProgressFileTreeData): Promise<Iterable<IChatResponseProgressFileTreeData>> {
223
return element.children ?? [];
224
}
225
}
226
227