From 2d0b88e904c65c8e4f066bd357440dd4aa146adc Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 13 Feb 2026 23:58:21 +0000 Subject: [PATCH] Remember chat editor scroll position when switching tabs (#295278) --- .../contrib/chat/browser/widget/chatWidget.ts | 8 ++++ .../browser/widgetHosts/editor/chatEditor.ts | 44 ++++++++++++++++--- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts index 32024aabc22..bc7df9a2613 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts @@ -634,6 +634,14 @@ export class ChatWidget extends Disposable implements IChatWidget { return this.input.height.get() + this.listWidget.contentHeight + this.chatSuggestNextWidget.height; } + get scrollTop(): number { + return this.listWidget.scrollTop; + } + + set scrollTop(value: number) { + this.listWidget.scrollTop = value; + } + get attachmentModel(): ChatAttachmentModel { return this.input.attachmentModel; } diff --git a/src/vs/workbench/contrib/chat/browser/widgetHosts/editor/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/widgetHosts/editor/chatEditor.ts index fffb5a27aa8..8a1c71fb615 100644 --- a/src/vs/workbench/contrib/chat/browser/widgetHosts/editor/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/widgetHosts/editor/chatEditor.ts @@ -9,7 +9,9 @@ import { raceCancellationError } from '../../../../../../base/common/async.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; +import { URI } from '../../../../../../base/common/uri.js'; import * as nls from '../../../../../../nls.js'; +import { ITextResourceConfigurationService } from '../../../../../../editor/common/services/textResourceConfiguration.js'; import { IContextKeyService, IScopedContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IEditorOptions } from '../../../../../../platform/editor/common/editor.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; @@ -18,10 +20,12 @@ import { IStorageService } from '../../../../../../platform/storage/common/stora import { ITelemetryService } from '../../../../../../platform/telemetry/common/telemetry.js'; import { editorBackground, editorForeground, inputBackground } from '../../../../../../platform/theme/common/colorRegistry.js'; import { IThemeService } from '../../../../../../platform/theme/common/themeService.js'; -import { EditorPane } from '../../../../../browser/parts/editor/editorPane.js'; +import { AbstractEditorWithViewState } from '../../../../../browser/parts/editor/editorWithViewState.js'; import { IEditorOpenContext } from '../../../../../common/editor.js'; +import { EditorInput } from '../../../../../common/editor/editorInput.js'; import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../../../../common/theme.js'; -import { IEditorGroup } from '../../../../../services/editor/common/editorGroupsService.js'; +import { IEditorGroup, IEditorGroupsService } from '../../../../../services/editor/common/editorGroupsService.js'; +import { IEditorService } from '../../../../../services/editor/common/editorService.js'; import { ChatContextKeys } from '../../../common/actions/chatContextKeys.js'; import { IChatModel, IChatModelInputState, IExportableChatData, ISerializableChatData } from '../../../common/model/chatModel.js'; import { IChatService } from '../../../common/chatService/chatService.js'; @@ -45,7 +49,13 @@ export interface IChatEditorOptions extends IEditorOptions { }; } -export class ChatEditor extends EditorPane { +export interface IChatEditorViewState { + scrollTop: number; +} + +export class ChatEditor extends AbstractEditorWithViewState { + private static readonly VIEW_STATE_KEY = 'chatEditorViewState'; + private _widget!: ChatWidget; public get widget(): ChatWidget { return this._widget; @@ -63,13 +73,16 @@ export class ChatEditor extends EditorPane { group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IChatService private readonly chatService: IChatService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, ) { - super(ChatEditorInput.EditorID, group, telemetryService, themeService, storageService); + super(ChatEditorInput.EditorID, group, ChatEditor.VIEW_STATE_KEY, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService); } private async clear() { @@ -138,7 +151,6 @@ export class ChatEditor extends EditorPane { } override clearInput(): void { - this.saveState(); this.widget.setModel(undefined); super.clearInput(); } @@ -242,6 +254,11 @@ export class ChatEditor extends EditorPane { this.updateModel(editorModel.model); + const viewState = this.loadEditorViewState(input, context); + if (viewState) { + this._widget.scrollTop = viewState.scrollTop; + } + if (isContributedChatSession && options?.title?.preferred && input.sessionResource) { this.chatService.setChatSessionTitle(input.sessionResource, options.title.preferred); } @@ -255,6 +272,21 @@ export class ChatEditor extends EditorPane { this.widget.setModel(model); } + protected computeEditorViewState(_resource: URI): IChatEditorViewState | undefined { + if (!this._widget) { + return undefined; + } + return { scrollTop: this._widget.scrollTop }; + } + + protected tracksEditorViewState(input: EditorInput): boolean { + return input instanceof ChatEditorInput; + } + + protected toEditorViewStateResource(input: EditorInput): URI | undefined { + return (input as ChatEditorInput).sessionResource; + } + override layout(dimension: dom.Dimension, position?: dom.IDomPosition | undefined): void { this.dimension = dimension; if (this.widget) {