diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 11b8f8de857..da0282f46be 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -170,6 +170,16 @@ export class InlineChatController implements IEditorContribution { await this.run({ existingSession }); })); + this._store.add(this._inlineChatSessionService.onDidEndSession(e => { + if (e.session === this._session && e.endedByExternalCause) { + this._log('session ENDED by external cause'); + this._session = undefined; + this._strategy?.cancel(); + this._resetWidget(); + this.cancelSession(); + } + })); + this._store.add(this._inlineChatSessionService.onDidMoveSession(async e => { if (e.editor === this._editor) { this._log('session RESUMING after move', e); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts index 0f2f0fb4142..638f0858903 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts @@ -31,13 +31,17 @@ export interface IInlineChatSessionEvent { readonly session: Session; } +export interface IInlineChatSessionEndEvent extends IInlineChatSessionEvent { + readonly endedByExternalCause: boolean; +} + export interface IInlineChatSessionService { _serviceBrand: undefined; onWillStartSession: Event; onDidMoveSession: Event; onDidStashSession: Event; - onDidEndSession: Event; + onDidEndSession: Event; createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: IRange }, token: CancellationToken): Promise; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 59ac9bf22ca..ad54eae0667 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -16,7 +16,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Iterable } from 'vs/base/common/iterator'; import { raceCancellation } from 'vs/base/common/async'; -import { Recording, IInlineChatSessionService, ISessionKeyComputer, IInlineChatSessionEvent } from './inlineChatSessionService'; +import { Recording, IInlineChatSessionService, ISessionKeyComputer, IInlineChatSessionEvent, IInlineChatSessionEndEvent } from './inlineChatSessionService'; import { HunkData, Session, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { ITextModel, IValidEditOperation } from 'vs/editor/common/model'; @@ -43,8 +43,8 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { private readonly _onDidMoveSession = new Emitter(); readonly onDidMoveSession: Event = this._onDidMoveSession.event; - private readonly _onDidEndSession = new Emitter(); - readonly onDidEndSession: Event = this._onDidEndSession.event; + private readonly _onDidEndSession = new Emitter(); + readonly onDidEndSession: Event = this._onDidEndSession.event; private readonly _onDidStashSession = new Emitter(); readonly onDidStashSession: Event = this._onDidStashSession.event; @@ -100,9 +100,16 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { } this._logService.trace('[IE] NEW session', provider.debugName); - this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.debugName}`); + this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.debugName}`); const store = new DisposableStore(); + store.add(this._inlineChatService.onDidChangeProviders(e => { + if (e.removed === provider) { + this._logService.trace(`[IE] provider GONE for ${editor.getId()}, ${provider.debugName}`); + this._releaseSession(session, true); + } + })); + const id = generateUuid(); const targetUri = textModel.uri; @@ -131,7 +138,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { if (targetUri.scheme === Schemas.untitled) { store.add(this._editorService.onDidCloseEditor(() => { if (!this._editorService.isOpened({ resource: targetUri, typeId: UntitledTextEditorInput.ID, editorId: DEFAULT_EDITOR_ASSOCIATION.id })) { - this.releaseSession(session); + this._releaseSession(session, true); } })); } @@ -141,6 +148,11 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { wholeRange = rawSession.wholeRange ? Range.lift(rawSession.wholeRange) : editor.getSelection(); } + if (token.isCancellationRequested) { + store.dispose(); + return undefined; + } + const session = new Session( options.editMode, targetUri, @@ -190,6 +202,10 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { } releaseSession(session: Session): void { + this._releaseSession(session, false); + } + + private _releaseSession(session: Session, byServer: boolean): void { let tuple: [string, SessionData] | undefined; @@ -211,11 +227,11 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData()); const [key, value] = tuple; - value.store.dispose(); this._sessions.delete(key); this._logService.trace(`[IE] did RELEASED session for ${value.editor.getId()}, ${session.provider.debugName}`); - this._onDidEndSession.fire({ editor: value.editor, session }); + this._onDidEndSession.fire({ editor: value.editor, session, endedByExternalCause: byServer }); + value.store.dispose(); } stashSession(session: Session, editor: ICodeEditor, undoCancelEdits: IValidEditOperation[]): StashedSession { diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index bf8c26d3a7d..0794a0f8a15 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -115,10 +115,15 @@ export interface IInlineChatSessionProvider { export const IInlineChatService = createDecorator('IInlineChatService'); +export interface InlineChatProviderChangeEvent { + readonly added?: IInlineChatSessionProvider; + readonly removed?: IInlineChatSessionProvider; +} + export interface IInlineChatService { _serviceBrand: undefined; - onDidChangeProviders: Event; + onDidChangeProviders: Event; addProvider(provider: IInlineChatSessionProvider): IDisposable; getAllProvider(): Iterable; } diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl.ts index 63b079cb0c2..ddff44a1b7f 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl.ts @@ -7,20 +7,17 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; import { LinkedList } from 'vs/base/common/linkedList'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IInlineChatService, IInlineChatSessionProvider, CTX_INLINE_CHAT_HAS_PROVIDER } from './inlineChat'; +import { IInlineChatService, IInlineChatSessionProvider, CTX_INLINE_CHAT_HAS_PROVIDER, InlineChatProviderChangeEvent } from './inlineChat'; export class InlineChatServiceImpl implements IInlineChatService { declare _serviceBrand: undefined; + private readonly _onDidChangeProviders = new Emitter(); private readonly _entries = new LinkedList(); - private readonly _ctxHasProvider: IContextKey; - private readonly _onDidChangeProviders = new Emitter(); - public get onDidChangeProviders() { - return this._onDidChangeProviders.event; - } + readonly onDidChangeProviders = this._onDidChangeProviders.event; constructor(@IContextKeyService contextKeyService: IContextKeyService) { this._ctxHasProvider = CTX_INLINE_CHAT_HAS_PROVIDER.bindTo(contextKeyService); @@ -30,12 +27,12 @@ export class InlineChatServiceImpl implements IInlineChatService { const rm = this._entries.push(provider); this._ctxHasProvider.set(true); - this._onDidChangeProviders.fire(); + this._onDidChangeProviders.fire({ added: provider }); return toDisposable(() => { rm(); this._ctxHasProvider.set(this._entries.size > 0); - this._onDidChangeProviders.fire(); + this._onDidChangeProviders.fire({ removed: provider }); }); }