Path: blob/main/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTreeContentPart.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../../../../base/browser/dom.js';6import { IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js';7import { ITreeCompressionDelegate } from '../../../../../base/browser/ui/tree/asyncDataTree.js';8import { ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compressedObjectTreeModel.js';9import { ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js';10import { IAsyncDataSource, ITreeNode } from '../../../../../base/browser/ui/tree/tree.js';11import { Emitter, Event } from '../../../../../base/common/event.js';12import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';13import { localize } from '../../../../../nls.js';14import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';15import { FileKind, FileType } from '../../../../../platform/files/common/files.js';16import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';17import { WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js';18import { IOpenerService } from '../../../../../platform/opener/common/opener.js';19import { IThemeService } from '../../../../../platform/theme/common/themeService.js';20import { IResourceLabel, ResourceLabels } from '../../../../browser/labels.js';21import { ChatTreeItem } from '../chat.js';22import { IDisposableReference, ResourcePool } from './chatCollections.js';23import { IChatContentPart } from './chatContentParts.js';24import { IChatProgressRenderableResponseContent } from '../../common/chatModel.js';25import { IChatResponseProgressFileTreeData } from '../../common/chatService.js';26import { createFileIconThemableTreeContainerScope } from '../../../files/browser/views/explorerView.js';27import { IFilesConfiguration } from '../../../files/common/files.js';2829const $ = dom.$;3031export class ChatTreeContentPart extends Disposable implements IChatContentPart {32public readonly domNode: HTMLElement;3334private readonly _onDidChangeHeight = this._register(new Emitter<void>());35public readonly onDidChangeHeight = this._onDidChangeHeight.event;3637public readonly onDidFocus: Event<void>;3839private tree: WorkbenchCompressibleAsyncDataTree<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData, void>;4041constructor(42data: IChatResponseProgressFileTreeData,43element: ChatTreeItem,44treePool: TreePool,45treeDataIndex: number,46@IOpenerService private readonly openerService: IOpenerService47) {48super();4950const ref = this._register(treePool.get());51this.tree = ref.object;52this.onDidFocus = this.tree.onDidFocus;5354this._register(this.tree.onDidOpen((e) => {55if (e.element && !('children' in e.element)) {56this.openerService.open(e.element.uri);57}58}));59this._register(this.tree.onDidChangeCollapseState(() => {60this._onDidChangeHeight.fire();61}));62this._register(this.tree.onContextMenu((e) => {63e.browserEvent.preventDefault();64e.browserEvent.stopPropagation();65}));6667this.tree.setInput(data).then(() => {68if (!ref.isStale()) {69this.tree.layout();70this._onDidChangeHeight.fire();71}72});7374this.domNode = this.tree.getHTMLElement().parentElement!;75}7677domFocus() {78this.tree.domFocus();79}8081hasSameContent(other: IChatProgressRenderableResponseContent): boolean {82// No other change allowed for this content type83return other.kind === 'treeData';84}8586addDisposable(disposable: IDisposable): void {87this._register(disposable);88}89}9091export class TreePool extends Disposable {92private _pool: ResourcePool<WorkbenchCompressibleAsyncDataTree<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData, void>>;9394public get inUse(): ReadonlySet<WorkbenchCompressibleAsyncDataTree<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData, void>> {95return this._pool.inUse;96}9798constructor(99private _onDidChangeVisibility: Event<boolean>,100@IInstantiationService private readonly instantiationService: IInstantiationService,101@IConfigurationService private readonly configService: IConfigurationService,102@IThemeService private readonly themeService: IThemeService,103) {104super();105this._pool = this._register(new ResourcePool(() => this.treeFactory()));106}107108private treeFactory(): WorkbenchCompressibleAsyncDataTree<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData, void> {109const resourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility }));110111const container = $('.interactive-response-progress-tree');112this._register(createFileIconThemableTreeContainerScope(container, this.themeService));113114const tree = this.instantiationService.createInstance(115WorkbenchCompressibleAsyncDataTree<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData>,116'ChatListRenderer',117container,118new ChatListTreeDelegate(),119new ChatListTreeCompressionDelegate(),120[new ChatListTreeRenderer(resourceLabels, this.configService.getValue('explorer.decorations'))],121new ChatListTreeDataSource(),122{123collapseByDefault: () => false,124expandOnlyOnTwistieClick: () => false,125identityProvider: {126getId: (e: IChatResponseProgressFileTreeData) => e.uri.toString()127},128accessibilityProvider: {129getAriaLabel: (element: IChatResponseProgressFileTreeData) => element.label,130getWidgetAriaLabel: () => localize('treeAriaLabel', "File Tree")131},132alwaysConsumeMouseWheel: false133});134135return tree;136}137138get(): IDisposableReference<WorkbenchCompressibleAsyncDataTree<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData, void>> {139const object = this._pool.get();140let stale = false;141return {142object,143isStale: () => stale,144dispose: () => {145stale = true;146this._pool.release(object);147}148};149}150}151152class ChatListTreeDelegate implements IListVirtualDelegate<IChatResponseProgressFileTreeData> {153static readonly ITEM_HEIGHT = 22;154155getHeight(element: IChatResponseProgressFileTreeData): number {156return ChatListTreeDelegate.ITEM_HEIGHT;157}158159getTemplateId(element: IChatResponseProgressFileTreeData): string {160return 'chatListTreeTemplate';161}162}163164class ChatListTreeCompressionDelegate implements ITreeCompressionDelegate<IChatResponseProgressFileTreeData> {165isIncompressible(element: IChatResponseProgressFileTreeData): boolean {166return !element.children;167}168}169170interface IChatListTreeRendererTemplate {171templateDisposables: DisposableStore;172label: IResourceLabel;173}174175class ChatListTreeRenderer implements ICompressibleTreeRenderer<IChatResponseProgressFileTreeData, void, IChatListTreeRendererTemplate> {176templateId: string = 'chatListTreeTemplate';177178constructor(private labels: ResourceLabels, private decorations: IFilesConfiguration['explorer']['decorations']) { }179180renderCompressedElements(element: ITreeNode<ICompressedTreeNode<IChatResponseProgressFileTreeData>, void>, index: number, templateData: IChatListTreeRendererTemplate): void {181templateData.label.element.style.display = 'flex';182const label = element.element.elements.map((e) => e.label);183templateData.label.setResource({ resource: element.element.elements[0].uri, name: label }, {184title: element.element.elements[0].label,185fileKind: element.children ? FileKind.FOLDER : FileKind.FILE,186extraClasses: ['explorer-item'],187fileDecorations: this.decorations188});189}190renderTemplate(container: HTMLElement): IChatListTreeRendererTemplate {191const templateDisposables = new DisposableStore();192const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true }));193return { templateDisposables, label };194}195renderElement(element: ITreeNode<IChatResponseProgressFileTreeData, void>, index: number, templateData: IChatListTreeRendererTemplate): void {196templateData.label.element.style.display = 'flex';197if (!element.children.length && element.element.type !== FileType.Directory) {198templateData.label.setFile(element.element.uri, {199fileKind: FileKind.FILE,200hidePath: true,201fileDecorations: this.decorations,202});203} else {204templateData.label.setResource({ resource: element.element.uri, name: element.element.label }, {205title: element.element.label,206fileKind: FileKind.FOLDER,207fileDecorations: this.decorations208});209}210}211disposeTemplate(templateData: IChatListTreeRendererTemplate): void {212templateData.templateDisposables.dispose();213}214}215216class ChatListTreeDataSource implements IAsyncDataSource<IChatResponseProgressFileTreeData, IChatResponseProgressFileTreeData> {217hasChildren(element: IChatResponseProgressFileTreeData): boolean {218return !!element.children;219}220221async getChildren(element: IChatResponseProgressFileTreeData): Promise<Iterable<IChatResponseProgressFileTreeData>> {222return element.children ?? [];223}224}225226227