Path: blob/main/src/vs/workbench/contrib/chat/browser/chatEditor.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 { raceCancellationError } from '../../../../base/common/async.js';7import { CancellationToken } from '../../../../base/common/cancellation.js';8import { IContextKeyService, IScopedContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';9import { IEditorOptions } from '../../../../platform/editor/common/editor.js';10import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';11import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';12import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';13import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';14import { editorBackground, editorForeground, inputBackground } from '../../../../platform/theme/common/colorRegistry.js';15import { IThemeService } from '../../../../platform/theme/common/themeService.js';16import { EditorPane } from '../../../browser/parts/editor/editorPane.js';17import { IEditorOpenContext } from '../../../common/editor.js';18import { Memento } from '../../../common/memento.js';19import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js';20import { IEditorGroup } from '../../../services/editor/common/editorGroupsService.js';21import { ChatContextKeys } from '../common/chatContextKeys.js';22import { IChatModel, IExportableChatData, ISerializableChatData } from '../common/chatModel.js';23import { CHAT_PROVIDER_ID } from '../common/chatParticipantContribTypes.js';24import { IChatSessionsService } from '../common/chatSessionsService.js';25import { ChatAgentLocation, ChatModeKind } from '../common/constants.js';26import { clearChatEditor } from './actions/chatClear.js';27import { ChatEditorInput } from './chatEditorInput.js';28import { getChatSessionType } from './chatSessions/common.js';29import { ChatWidget, IChatViewState } from './chatWidget.js';3031export interface IChatEditorOptions extends IEditorOptions {32target?: { sessionId: string } | { data: IExportableChatData | ISerializableChatData };33preferredTitle?: string;34ignoreInView?: boolean;35}3637export class ChatEditor extends EditorPane {38private _widget!: ChatWidget;39public get widget(): ChatWidget {40return this._widget;41}42private _scopedContextKeyService!: IScopedContextKeyService;43override get scopedContextKeyService() {44return this._scopedContextKeyService;45}4647private _memento: Memento | undefined;48private _viewState: IChatViewState | undefined;49private dimension = new dom.Dimension(0, 0);5051constructor(52group: IEditorGroup,53@ITelemetryService telemetryService: ITelemetryService,54@IThemeService themeService: IThemeService,55@IInstantiationService private readonly instantiationService: IInstantiationService,56@IStorageService private readonly storageService: IStorageService,57@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,58@IContextKeyService private readonly contextKeyService: IContextKeyService,59) {60super(ChatEditorInput.EditorID, group, telemetryService, themeService, storageService);61}6263private async clear() {64if (this.input) {65return this.instantiationService.invokeFunction(clearChatEditor, this.input as ChatEditorInput);66}67}6869protected override createEditor(parent: HTMLElement): void {70this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent));71const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])));72ChatContextKeys.inChatEditor.bindTo(this._scopedContextKeyService).set(true);7374this._widget = this._register(75scopedInstantiationService.createInstance(76ChatWidget,77ChatAgentLocation.Panel,78undefined,79{80autoScroll: mode => mode !== ChatModeKind.Ask,81renderFollowups: true,82supportsFileReferences: true,83rendererOptions: {84renderTextEditsAsSummary: (uri) => {85return true;86},87referencesExpandedWhenEmptyResponse: false,88progressMessageAtBottomOfResponse: mode => mode !== ChatModeKind.Ask,89},90enableImplicitContext: true,91enableWorkingSet: 'explicit',92supportsChangingModes: true,93},94{95listForeground: editorForeground,96listBackground: editorBackground,97overlayBackground: EDITOR_DRAG_AND_DROP_BACKGROUND,98inputEditorBackground: inputBackground,99resultEditorBackground: editorBackground100}));101this._register(this.widget.onDidClear(() => this.clear()));102this.widget.render(parent);103this.widget.setVisible(true);104}105106protected override setEditorVisible(visible: boolean): void {107super.setEditorVisible(visible);108109this.widget?.setVisible(visible);110111if (visible && this.widget) {112this.widget.layout(this.dimension.height, this.dimension.width);113}114}115116public override focus(): void {117super.focus();118119this.widget?.focusInput();120}121122override clearInput(): void {123this.saveState();124super.clearInput();125}126127override async setInput(input: ChatEditorInput, options: IChatEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {128await super.setInput(input, options, context, token);129if (token.isCancellationRequested) {130return;131}132133if (!this.widget) {134throw new Error('ChatEditor lifecycle issue: no editor widget');135}136137let isContributedChatSession = false;138const chatSessionType = getChatSessionType(input);139if (chatSessionType !== 'local') {140await raceCancellationError(this.chatSessionsService.canResolveContentProvider(chatSessionType), token);141const contributions = this.chatSessionsService.getAllChatSessionContributions();142const contribution = contributions.find(c => c.type === chatSessionType);143if (contribution) {144this.widget.lockToCodingAgent(contribution.name, contribution.displayName, contribution.type);145isContributedChatSession = true;146} else {147this.widget.unlockFromCodingAgent();148}149} else {150this.widget.unlockFromCodingAgent();151}152153const editorModel = await raceCancellationError(input.resolve(), token);154155if (!editorModel) {156throw new Error(`Failed to get model for chat editor. id: ${input.sessionId}`);157}158const viewState = options?.viewState ?? input.options.viewState;159this.updateModel(editorModel.model, viewState);160161if (isContributedChatSession && options?.preferredTitle) {162editorModel.model.setCustomTitle(options?.preferredTitle);163}164}165166private updateModel(model: IChatModel, viewState?: IChatViewState): void {167this._memento = new Memento('interactive-session-editor-' + CHAT_PROVIDER_ID, this.storageService);168this._viewState = viewState ?? this._memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IChatViewState;169this.widget.setModel(model, { ...this._viewState });170}171172protected override saveState(): void {173this.widget?.saveState();174175if (this._memento && this._viewState) {176const widgetViewState = this.widget.getViewState();177178// Need to set props individually on the memento179this._viewState.inputValue = widgetViewState.inputValue;180this._viewState.inputState = widgetViewState.inputState;181this._memento.saveMemento();182}183}184185override getViewState(): object | undefined {186return { ...this._viewState };187}188189override layout(dimension: dom.Dimension, position?: dom.IDomPosition | undefined): void {190this.dimension = dimension;191if (this.widget) {192this.widget.layout(dimension.height, dimension.width);193}194}195}196197198