diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 0e7922658051c..11862c1b29956 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1940,6 +1940,28 @@ export class DragAndDropObserver extends Disposable { } } +/** + * A wrapper around ResizeObserver that is disposable. + */ +export class DisposableResizeObserver extends Disposable { + + private readonly observer: ResizeObserver; + + constructor(callback: ResizeObserverCallback) { + super(); + this.observer = new ResizeObserver(callback); + this._register(toDisposable(() => this.observer.disconnect())); + } + + observe(target: Element, options?: ResizeObserverOptions): void { + this.observer.observe(target, options); + } + + unobserve(target: Element): void { + this.observer.unobserve(target); + } +} + type HTMLElementAttributeKeys = Partial<{ [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? HTMLElementAttributeKeys : T[K] }>; type ElementAttributes = HTMLElementAttributeKeys & Record; type RemoveHTMLElement = T extends HTMLElement ? never : T; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 6cfbb62d07ccf..ea078db10b628 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -3126,8 +3126,8 @@ export namespace ChatResponsePart { export namespace ChatAgentRequest { export function to(request: IChatAgentRequest, location2: vscode.ChatRequestEditorData | vscode.ChatRequestNotebookData | undefined, model: vscode.LanguageModelChat, diagnostics: readonly [vscode.Uri, readonly vscode.Diagnostic[]][], tools: Map, extension: IRelaxedExtensionDescription, logService: ILogService): vscode.ChatRequest { - const toolReferences: typeof request.variables.variables = []; - const variableReferences: typeof request.variables.variables = []; + const toolReferences: IChatRequestVariableEntry[] = []; + const variableReferences: IChatRequestVariableEntry[] = []; for (const v of request.variables.variables) { if (v.kind === 'tool') { toolReferences.push(v); diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index 1b33b025fdddd..3a687ad965a6b 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -176,6 +176,11 @@ export function getVisbileViewContextKey(viewId: string): string { return `view. //#region < --- Resources --- > abstract class AbstractResourceContextKey { + + // NOTE: DO NOT CHANGE THE DEFAULT VALUE TO ANYTHING BUT + // UNDEFINED! IT IS IMPORTANT THAT DEFAULTS ARE INHERITED + // FROM THE PARENT CONTEXT AND ONLY UNDEFINED DOES THIS + static readonly Scheme = new RawContextKey('resourceScheme', undefined, { type: 'string', description: localize('resourceScheme', "The scheme of the resource") }); static readonly Filename = new RawContextKey('resourceFilename', undefined, { type: 'string', description: localize('resourceFilename', "The file name of the resource") }); static readonly Dirname = new RawContextKey('resourceDirname', undefined, { type: 'string', description: localize('resourceDirname', "The folder name the resource is contained in") }); @@ -186,8 +191,6 @@ abstract class AbstractResourceContextKey { static readonly HasResource = new RawContextKey('resourceSet', undefined, { type: 'boolean', description: localize('resourceSet', "Whether a resource is present or not") }); static readonly IsFileSystemResource = new RawContextKey('isFileSystemResource', undefined, { type: 'boolean', description: localize('isFileSystemResource', "Whether the resource is backed by a file system provider") }); - protected readonly _disposables = new DisposableStore(); - protected _value: URI | undefined; protected readonly _resourceKey: IContextKey; protected readonly _schemeKey: IContextKey; @@ -216,10 +219,6 @@ abstract class AbstractResourceContextKey { this._isFileSystemResource = AbstractResourceContextKey.IsFileSystemResource.bindTo(this._contextKeyService); } - dispose(): void { - this._disposables.dispose(); - } - protected _setLangId(): void { const value = this.get(); if (!value) { @@ -277,6 +276,9 @@ abstract class AbstractResourceContextKey { } export class ResourceContextKey extends AbstractResourceContextKey { + + private readonly _disposables = new DisposableStore(); + constructor( @IContextKeyService contextKeyService: IContextKeyService, @IFileService fileService: IFileService, @@ -299,12 +301,18 @@ export class ResourceContextKey extends AbstractResourceContextKey { } })); } -} -export class StaticResourceContextKey extends AbstractResourceContextKey { - // No event listeners + dispose(): void { + this._disposables.dispose(); + } } +/** + * This is a version of ResourceContextKey that is not disposable and has no listeners for model change events. + * It will configure itself for the state/presence of a model only when created and not update. + */ +export class StaticResourceContextKey extends AbstractResourceContextKey { } + //#endregion diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatInlineAnchorWidget.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatInlineAnchorWidget.ts index 2030a6f56383d..0a95042c83c6b 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatInlineAnchorWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatInlineAnchorWidget.ts @@ -236,7 +236,7 @@ export class InlineAnchorWidget extends Disposable { } } - const resourceContextKey = this._register(new StaticResourceContextKey(contextKeyService, fileService, languageService, modelService)); + const resourceContextKey = new StaticResourceContextKey(contextKeyService, fileService, languageService, modelService); resourceContextKey.set(location.uri); this._chatResourceContext.set(location.uri.toString()); diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts index 2298702f2b7c5..9aa2abdfe711b 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts @@ -612,7 +612,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } get contentHeight(): number { - return this.input.contentHeight + this.tree.contentHeight + this.chatSuggestNextWidget.height; + return this.input.inputPartHeight.get() + this.tree.contentHeight + this.chatSuggestNextWidget.height; } get attachmentModel(): ChatAttachmentModel { @@ -1921,7 +1921,9 @@ export class ChatWidget extends Disposable implements IChatWidget { }, }); })); - this._register(this.input.onDidChangeHeight(() => { + this._register(autorun(reader => { + this.input.inputPartHeight.read(reader); + const editedRequest = this.renderer.getTemplateDataForRequestId(this.viewModel?.editing?.id); if (isRequestVM(editedRequest?.currentElement) && this.viewModel?.editing) { this.renderer.updateItemHeightOnRender(editedRequest?.currentElement, editedRequest); @@ -2422,14 +2424,13 @@ export class ChatWidget extends Disposable implements IChatWidget { const heightUpdated = this.bodyDimension && this.bodyDimension.height !== height; this.bodyDimension = new dom.Dimension(width, height); - const layoutHeight = this._dynamicMessageLayoutData?.enabled ? this._dynamicMessageLayoutData.maxHeight : height; if (this.viewModel?.editing) { - this.inlineInputPart?.layout(layoutHeight, width); + this.inlineInputPart?.layout(width); } - this.inputPart.layout(layoutHeight, width); + this.inputPart.layout(width); - const inputHeight = this.inputPart.inputPartHeight; + const inputHeight = this.inputPart.inputPartHeight.get(); const chatSuggestNextWidgetHeight = this.chatSuggestNextWidget.height; const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight - 2; const lastItem = this.viewModel?.getItems().at(-1); @@ -2487,8 +2488,8 @@ export class ChatWidget extends Disposable implements IChatWidget { const possibleMaxHeight = (this._dynamicMessageLayoutData?.maxHeight ?? maxHeight); const width = this.bodyDimension?.width ?? this.container.offsetWidth; - this.input.layout(possibleMaxHeight, width); - const inputPartHeight = this.input.inputPartHeight; + this.input.layout(width); + const inputPartHeight = this.input.inputPartHeight.get(); const chatSuggestNextWidgetHeight = this.chatSuggestNextWidget.height; const newHeight = Math.min(renderHeight + diff, possibleMaxHeight - inputPartHeight - chatSuggestNextWidgetHeight); this.layout(newHeight + inputPartHeight + chatSuggestNextWidgetHeight, width); @@ -2532,8 +2533,8 @@ export class ChatWidget extends Disposable implements IChatWidget { } const width = this.bodyDimension?.width ?? this.container.offsetWidth; - this.input.layout(this._dynamicMessageLayoutData.maxHeight, width); - const inputHeight = this.input.inputPartHeight; + this.input.layout(width); + const inputHeight = this.input.inputPartHeight.get(); const chatSuggestNextWidgetHeight = this.chatSuggestNextWidget.height; const totalMessages = this.viewModel.getItems(); diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts index 33d2fc18eb4c7..2db1c584e4b18 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts @@ -187,9 +187,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private _onDidLoadInputState: Emitter = this._register(new Emitter()); readonly onDidLoadInputState: Event = this._onDidLoadInputState.event; - private _onDidChangeHeight = this._register(new Emitter()); - readonly onDidChangeHeight: Event = this._onDidChangeHeight.event; - private _onDidFocus = this._register(new Emitter()); readonly onDidFocus: Event = this._onDidFocus.event; @@ -272,32 +269,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private chatInputWidgetsContainer!: HTMLElement; private readonly _widgetController = this._register(new MutableDisposable()); - private _inputPartHeight: number = 0; - get inputPartHeight() { - return this._inputPartHeight; - } - - private _followupsHeight: number = 0; - get followupsHeight() { - return this._followupsHeight; - } - - private _editSessionWidgetHeight: number = 0; - get editSessionWidgetHeight() { - return this._editSessionWidgetHeight; - } - - get todoListWidgetHeight() { - return this.chatInputTodoListWidgetContainer.offsetHeight; - } - - get inputWidgetsHeight() { - return this.chatInputWidgetsContainer?.offsetHeight ?? 0; - } - - get attachmentsHeight() { - return this.attachmentsContainer.offsetHeight + (this.attachmentsContainer.checkVisibility() ? 6 : 0); - } + readonly inputPartHeight = observableValue(this, 0); private _inputEditor!: CodeEditorWidget; private _inputEditorElement!: HTMLElement; @@ -408,7 +380,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge }; } - private cachedDimensions: dom.Dimension | undefined; + private cachedWidth: number | undefined; private cachedExecuteToolbarWidth: number | undefined; private cachedInputToolbarWidth: number | undefined; @@ -935,9 +907,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge public setCurrentLanguageModel(model: ILanguageModelChatMetadataAndIdentifier) { this._currentLanguageModel = model; - if (this.cachedDimensions) { + if (this.cachedWidth) { // For quick chat and editor chat, relayout because the input may need to shrink to accomodate the model name - this.layout(this.cachedDimensions.height, this.cachedDimensions.width); + this.layout(this.cachedWidth); } // Store as global user preference (session-specific state is in the model's inputModel) @@ -1621,7 +1593,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge if (!this._widgetController.value) { this._widgetController.value = this.instantiationService.createInstance(ChatInputPartWidgetController, this.chatInputWidgetsContainer); - this._register(this._widgetController.value.onDidChangeHeight(() => this._onDidChangeHeight.fire())); } } @@ -1803,7 +1774,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const currentHeight = Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight); if (currentHeight !== this.inputEditorHeight) { this.inputEditorHeight = currentHeight; - this._onDidChangeHeight.fire(); + // Directly update editor layout - ResizeObserver will notify parent about height change + if (this.cachedWidth) { + this._layout(this.cachedWidth); + } } const model = this._inputEditor.getModel(); @@ -1816,7 +1790,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._register(this._inputEditor.onDidContentSizeChange(e => { if (e.contentHeightChanged) { this.inputEditorHeight = !this.inline ? e.contentHeight : this.inputEditorHeight; - this._onDidChangeHeight.fire(); + // Directly update editor layout - ResizeObserver will notify parent about height change + if (this.cachedWidth) { + this._layout(this.cachedWidth); + } } })); this._register(this._inputEditor.onDidFocusEditorText(() => { @@ -1907,8 +1884,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const container = toolbarElement.querySelector('.chat-sessionPicker-container'); this.chatSessionPickerContainer = container as HTMLElement | undefined; - if (this.cachedDimensions && typeof this.cachedInputToolbarWidth === 'number' && this.cachedInputToolbarWidth !== this.inputActionsToolbar.getItemsWidth()) { - this.layout(this.cachedDimensions.height, this.cachedDimensions.width); + if (this.cachedWidth && typeof this.cachedInputToolbarWidth === 'number' && this.cachedInputToolbarWidth !== this.inputActionsToolbar.getItemsWidth()) { + this.layout(this.cachedWidth); } })); this.executeToolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, toolbarsContainer, this.options.menus.executeToolbar, { @@ -1928,8 +1905,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.executeToolbar.getElement().classList.add('chat-execute-toolbar'); this.executeToolbar.context = { widget } satisfies IChatExecuteActionContext; this._register(this.executeToolbar.onDidChangeMenuItems(() => { - if (this.cachedDimensions && typeof this.cachedExecuteToolbarWidth === 'number' && this.cachedExecuteToolbarWidth !== this.executeToolbar.getItemsWidth()) { - this.layout(this.cachedDimensions.height, this.cachedDimensions.width); + if (this.cachedWidth && typeof this.cachedExecuteToolbarWidth === 'number' && this.cachedExecuteToolbarWidth !== this.executeToolbar.getItemsWidth()) { + this.layout(this.cachedWidth); } })); if (this.options.menus.inputSideToolbar) { @@ -2012,12 +1989,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } })); this.addFilesToolbar.context = { widget, placeholder: localize('chatAttachFiles', 'Search for files and context to add to your request') }; - this._register(this.addFilesToolbar.onDidChangeMenuItems(() => { - if (this.cachedDimensions) { - this._onDidChangeHeight.fire(); - } - })); this.renderAttachedContext(); + + const inputResizeObserver = this._register(new dom.DisposableResizeObserver(() => { + const newHeight = this.container.offsetHeight; + this.inputPartHeight.set(newHeight, undefined); + })); + inputResizeObserver.observe(this.container); } public toggleChatInputOverlay(editing: boolean): void { @@ -2035,8 +2013,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge public renderAttachedContext() { const container = this.attachedContextContainer; - // Note- can't measure attachedContextContainer, because it has `display: contents`, so measure the parent to check for height changes - const oldHeight = this.attachmentsContainer.offsetHeight; const store = new DisposableStore(); this.attachedContextDisposables.value = store; @@ -2128,10 +2104,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } } - if (oldHeight !== this.attachmentsContainer.offsetHeight) { - this._onDidChangeHeight.fire(); - } - this.addFilesButton?.setShowLabel(this._attachmentModel.size === 0 && !this.hasImplicitContextBlock()); this._indexOfLastOpenedContext = -1; @@ -2271,11 +2243,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge // Add the widget's DOM node to the dedicated todo list container dom.clearNode(this.chatInputTodoListWidgetContainer); dom.append(this.chatInputTodoListWidgetContainer, widget.domNode); - - // Listen to height changes - this._chatEditingTodosDisposables.add(widget.onDidChangeHeight(() => { - this._onDidChangeHeight.fire(); - })); } this._chatInputTodoListWidget.value.render(chatSessionResource); @@ -2387,8 +2354,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge dom.clearNode(this.chatEditingSessionWidgetContainer); this._chatEditsDisposables.clear(); this._chatEditList = undefined; - - this._onDidChangeHeight.fire(); } }); } @@ -2500,9 +2465,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._workingSetLinesRemovedSpan.value.textContent = `-${removed}`; dom.setVisibility(shouldShowEditingSession, this.chatEditingSessionWidgetContainer); - if (!shouldShowEditingSession) { - this._onDidChangeHeight.fire(); - } })); const countsContainer = dom.$('.working-set-line-counts'); @@ -2530,7 +2492,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const collapsed = this._workingSetCollapsed.read(reader); button.icon = collapsed ? Codicon.chevronRight : Codicon.chevronDown; workingSetContainer.classList.toggle('collapsed', collapsed); - this._onDidChangeHeight.fire(); })); if (!this._chatEditList) { @@ -2592,7 +2553,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge list.layout(height); list.getHTMLElement().style.height = `${height}px`; list.splice(0, list.length, allEntries); - this._onDidChangeHeight.fire(); })); } @@ -2650,7 +2610,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge group.remove(); })); } - this._onDidChangeHeight.fire(); } async renderFollowups(items: IChatFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise { @@ -2663,34 +2622,28 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge if (items && items.length > 0) { this.followupsDisposables.add(this.instantiationService.createInstance, ChatFollowups>(ChatFollowups, this.followupsContainer, items, this.location, undefined, followup => this._onDidAcceptFollowup.fire({ followup, response }))); } - this._onDidChangeHeight.fire(); } - get contentHeight(): number { - const data = this.getLayoutData(); - return data.followupsHeight + data.inputPartEditorHeight + data.inputPartVerticalPadding + data.inputEditorBorder + data.attachmentsHeight + data.toolbarsHeight + data.chatEditingStateHeight + data.todoListWidgetContainerHeight + data.inputWidgetsContainerHeight; - } - - layout(height: number, width: number) { - this.cachedDimensions = new dom.Dimension(width, height); + /** + * Layout the input part with the given width. Height is intrinsic - determined by content + * and detected via ResizeObserver, which updates `inputPartHeight` for the parent to observe. + */ + layout(width: number) { + this.cachedWidth = width; - return this._layout(height, width); + return this._layout(width); } private previousInputEditorDimension: IDimension | undefined; - private _layout(height: number, width: number, allowRecurse = true): void { + private _layout(width: number, allowRecurse = true): void { const data = this.getLayoutData(); - const inputEditorHeight = Math.min(data.inputPartEditorHeight, height - data.followupsHeight - data.attachmentsHeight - data.inputPartVerticalPadding - data.toolbarsHeight - data.chatEditingStateHeight - data.todoListWidgetContainerHeight - data.inputWidgetsContainerHeight); const followupsWidth = width - data.inputPartHorizontalPadding; this.followupsContainer.style.width = `${followupsWidth}px`; - this._inputPartHeight = data.inputPartVerticalPadding + data.followupsHeight + inputEditorHeight + data.inputEditorBorder + data.attachmentsHeight + data.toolbarsHeight + data.chatEditingStateHeight + data.todoListWidgetContainerHeight + data.inputWidgetsContainerHeight; - this._followupsHeight = data.followupsHeight; - this._editSessionWidgetHeight = data.chatEditingStateHeight; - const initialEditorScrollWidth = this._inputEditor.getScrollWidth(); const newEditorWidth = width - data.inputPartHorizontalPadding - data.editorBorder - data.inputPartHorizontalPaddingInside - data.toolbarsWidth - data.sideToolbarWidth; + const inputEditorHeight = Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight); const newDimension = { width: newEditorWidth, height: inputEditorHeight }; if (!this.previousInputEditorDimension || (this.previousInputEditorDimension.width !== newDimension.width || this.previousInputEditorDimension.height !== newDimension.height)) { // This layout call has side-effects that are hard to understand. eg if we are calling this inside a onDidChangeContent handler, this can trigger the next onDidChangeContent handler @@ -2701,7 +2654,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge if (allowRecurse && initialEditorScrollWidth < 10) { // This is probably the initial layout. Now that the editor is layed out with its correct width, it should report the correct contentHeight - return this._layout(height, width, false); + return this._layout(width, false); } } @@ -2728,20 +2681,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge }; return { - inputEditorBorder: 2, - followupsHeight: this.followupsContainer.offsetHeight, - inputPartEditorHeight: Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight), - inputPartHorizontalPadding: this.options.renderStyle === 'compact' ? 16 : 32, - inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : (16 /* entire part */ + 6 /* input container */ + (2 * 4) /* flex gap: todo|edits|input */), - attachmentsHeight: this.attachmentsHeight, editorBorder: 2, + inputPartHorizontalPadding: this.options.renderStyle === 'compact' ? 16 : 32, inputPartHorizontalPaddingInside: 12, toolbarsWidth: this.options.renderStyle === 'compact' ? getToolbarsWidthCompact() : 0, - toolbarsHeight: this.options.renderStyle === 'compact' ? 0 : 22, - chatEditingStateHeight: this.chatEditingSessionWidgetContainer.offsetHeight, sideToolbarWidth: inputSideToolbarWidth > 0 ? inputSideToolbarWidth + 4 /*gap*/ : 0, - todoListWidgetContainerHeight: this.chatInputTodoListWidgetContainer.offsetHeight, - inputWidgetsContainerHeight: this.inputWidgetsHeight, }; } } diff --git a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts index 7824c4fcf272e..52bfac795e0e9 100644 --- a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts @@ -548,7 +548,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { //#region Chat Control - private static readonly MIN_CHAT_WIDGET_HEIGHT = 120; + private static readonly MIN_CHAT_WIDGET_HEIGHT = 116; private _widget!: ChatWidget; get widget(): ChatWidget { return this._widget; } @@ -650,14 +650,10 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { })); // When showing sessions stacked, adjust the height of the sessions list to make room for chat input - let lastChatInputHeight: number | undefined; - this._register(chatWidget.input.onDidChangeHeight(() => { + this._register(autorun(reader => { + chatWidget.input.inputPartHeight.read(reader); if (this.sessionsViewerVisible && this.sessionsViewerOrientation === AgentSessionsViewerOrientation.Stacked) { - const chatInputHeight = this._widget?.input?.contentHeight; - if (chatInputHeight && chatInputHeight !== lastChatInputHeight) { // ensure we only layout on actual height changes - lastChatInputHeight = chatInputHeight; - this.relayout(); - } + this.relayout(); } })); } @@ -937,7 +933,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { let availableSessionsHeight = height - this.sessionsTitleContainer.offsetHeight; if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.Stacked) { - availableSessionsHeight -= Math.max(ChatViewPane.MIN_CHAT_WIDGET_HEIGHT, this._widget?.input?.contentHeight ?? 0); + availableSessionsHeight -= Math.max(ChatViewPane.MIN_CHAT_WIDGET_HEIGHT, this._widget?.input?.inputPartHeight.get() ?? 0); } else { availableSessionsHeight -= this.sessionsNewButtonContainer?.offsetHeight ?? 0; } diff --git a/src/vs/workbench/contrib/chat/common/model/chatModel.ts b/src/vs/workbench/contrib/chat/common/model/chatModel.ts index c32f835af684c..b752e95e0aed7 100644 --- a/src/vs/workbench/contrib/chat/common/model/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/model/chatModel.ts @@ -52,7 +52,7 @@ export function getAttachableImageExtension(mimeType: string): string | undefine } export interface IChatRequestVariableData { - variables: IChatRequestVariableEntry[]; + variables: readonly IChatRequestVariableEntry[]; } export namespace IChatRequestVariableData { @@ -64,6 +64,7 @@ export namespace IChatRequestVariableData { export interface IChatRequestModel { readonly id: string; readonly timestamp: number; + readonly version: number; readonly modeInfo?: IChatRequestModeInfo; readonly session: IChatModel; readonly message: IParsedChatRequest; @@ -329,6 +330,7 @@ export class ChatRequestModel implements IChatRequestModel { } public set variableData(v: IChatRequestVariableData) { + this._version++; this._variableData = v; } @@ -348,6 +350,11 @@ export class ChatRequestModel implements IChatRequestModel { return this._editedFileEvents; } + private _version = 0; + public get version(): number { + return this._version; + } + constructor(params: IChatRequestModelParameters) { this._session = params.session; this.message = params.message; diff --git a/src/vs/workbench/contrib/chat/common/model/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/model/chatViewModel.ts index c31571559bae9..12464c1fad868 100644 --- a/src/vs/workbench/contrib/chat/common/model/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/model/chatViewModel.ts @@ -5,7 +5,6 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; -import { hash } from '../../../../../base/common/hash.js'; import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, dispose } from '../../../../../base/common/lifecycle.js'; import { IObservable } from '../../../../../base/common/observable.js'; @@ -342,21 +341,16 @@ export class ChatViewModel extends Disposable implements IChatViewModel { } } -const variablesHash = new WeakMap(); - export class ChatRequestViewModel implements IChatRequestViewModel { get id() { return this._model.id; } + /** + * An ID that changes when the request should be re-rendered. + */ get dataId() { - let varsHash = variablesHash.get(this.variables); - if (typeof varsHash !== 'number') { - varsHash = hash(this.variables); - variablesHash.set(this.variables, varsHash); - } - - return `${this.id}_${this.isComplete ? '1' : '0'}_${varsHash}`; + return `${this.id}_${this._model.version + (this._model.response?.isComplete ? 1 : 0)}`; } /** @deprecated */ diff --git a/src/vs/workbench/contrib/chat/common/model/objectMutationLog.ts b/src/vs/workbench/contrib/chat/common/model/objectMutationLog.ts index 657400cd9d3fd..01ce16ae67a9b 100644 --- a/src/vs/workbench/contrib/chat/common/model/objectMutationLog.ts +++ b/src/vs/workbench/contrib/chat/common/model/objectMutationLog.ts @@ -92,7 +92,7 @@ export function value(comparator?: (a: R, b: R) => boolean): TransformValu } /** An array that will use the schema to compare items positionally. */ -export function array(schema: TransformObject | TransformValue): TransformArray { +export function array(schema: TransformObject | TransformValue): TransformArray { return { kind: TransformKind.Array, itemSchema: schema, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index c866191d832e5..d88b121fadd16 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -393,7 +393,7 @@ export class InlineChatWidget { let value = this.contentHeight; value -= this._chatWidget.contentHeight; - value += Math.min(this._chatWidget.input.contentHeight + maxWidgetOutputHeight, this._chatWidget.contentHeight); + value += Math.min(this._chatWidget.input.inputPartHeight.get() + maxWidgetOutputHeight, this._chatWidget.contentHeight); return value; }