Skip to content

Commit 9013a83

Browse files
authored
Merge pull request microsoft#183538 from microsoft/joh/available-roundworm
joh/available roundworm
2 parents ba158cc + ddbac48 commit 9013a83

File tree

5 files changed

+120
-50
lines changed

5 files changed

+120
-50
lines changed

src/vs/editor/browser/editorExtensions.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,9 +450,13 @@ export abstract class EditorAction2 extends Action2 {
450450
// precondition does hold
451451
return editor.invokeWithinContext((editorAccessor) => {
452452
const kbService = editorAccessor.get(IContextKeyService);
453-
if (kbService.contextMatchesRules(withNullAsUndefined(this.desc.precondition))) {
454-
return this.runEditorCommand(editorAccessor, editor!, ...args);
453+
const logService = editorAccessor.get(ILogService);
454+
const enabled = kbService.contextMatchesRules(withNullAsUndefined(this.desc.precondition));
455+
if (!enabled) {
456+
logService.debug(`[EditorAction2] NOT running command because its precondition is FALSE`, this.desc.id, this.desc.precondition?.serialize());
457+
return;
455458
}
459+
return this.runEditorCommand(editorAccessor, editor!, ...args);
456460
});
457461
}
458462

src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.contribution.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ import { IInteractiveEditorService, INTERACTIVE_EDITOR_ID } from 'vs/workbench/c
1111
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
1212
import { InteractiveEditorServiceImpl } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditorServiceImpl';
1313
import { IInteractiveEditorSessionService, InteractiveEditorSessionService } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorSession';
14+
import { Registry } from 'vs/platform/registry/common/platform';
15+
import { IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions';
16+
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
17+
import { InteractiveEditorNotebookContribution } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorNotebook';
1418

1519
registerSingleton(IInteractiveEditorService, InteractiveEditorServiceImpl, InstantiationType.Delayed);
1620
registerSingleton(IInteractiveEditorSessionService, InteractiveEditorSessionService, InstantiationType.Delayed);
1721

18-
registerEditorContribution(INTERACTIVE_EDITOR_ID, InteractiveEditorController, EditorContributionInstantiation.Lazy);
22+
registerEditorContribution(INTERACTIVE_EDITOR_ID, InteractiveEditorController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors
1923

2024
registerAction2(interactiveEditorActions.StartSessionAction);
2125
registerAction2(interactiveEditorActions.UnstashSessionAction);
@@ -42,3 +46,7 @@ registerAction2(interactiveEditorActions.FeebackUnhelpfulCommand);
4246
registerAction2(interactiveEditorActions.ApplyPreviewEdits);
4347

4448
registerAction2(interactiveEditorActions.CopyRecordings);
49+
50+
51+
Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench)
52+
.registerWorkbenchContribution(InteractiveEditorNotebookContribution, LifecyclePhase.Restored);

src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,26 @@ export class InteractiveEditorController implements IEditorContribution {
130130
return;
131131
}
132132

133-
this._logService.trace('[IE] session RESUMING');
133+
this._log('session RESUMING', e);
134134
await this._nextState(State.CREATE_SESSION, { existingSession });
135-
this._logService.trace('[IE] session done or paused');
135+
this._log('session done or paused');
136136
}));
137+
this._log('NEW controller');
137138
}
138139

139140
dispose(): void {
140141
this._stashedSession.clear();
141142
this._finishExistingSession();
142143
this._store.dispose();
144+
this._log('controller disposed');
145+
}
146+
147+
private _log(message: string | Error, ...more: any[]): void {
148+
if (message instanceof Error) {
149+
this._logService.error(message, ...more);
150+
} else {
151+
this._logService.trace(`[IE] (editor:${this._editor.getId()})${message}`, ...more);
152+
}
143153
}
144154

145155
getId(): string {
@@ -161,21 +171,21 @@ export class InteractiveEditorController implements IEditorContribution {
161171
}
162172

163173
async run(options: InteractiveEditorRunOptions | undefined): Promise<void> {
164-
this._logService.trace('[IE] session starting');
174+
this._log('session starting');
165175
await this._finishExistingSession();
166176
this._stashedSession.clear();
167177

168178
await this._nextState(State.CREATE_SESSION, options);
169-
this._logService.trace('[IE] session done or paused');
179+
this._log('session done or paused');
170180
}
171181

172182
private async _finishExistingSession(): Promise<void> {
173183
if (this._activeSession) {
174184
if (this._activeSession.editMode === EditMode.Preview) {
175-
this._logService.trace('[IE] finishing existing session, using CANCEL', this._activeSession.editMode);
185+
this._log('finishing existing session, using CANCEL', this._activeSession.editMode);
176186
await this.cancelSession();
177187
} else {
178-
this._logService.trace('[IE] finishing existing session, using APPLY', this._activeSession.editMode);
188+
this._log('finishing existing session, using APPLY', this._activeSession.editMode);
179189
await this.applyChanges();
180190
}
181191
}
@@ -184,7 +194,7 @@ export class InteractiveEditorController implements IEditorContribution {
184194
// ---- state machine
185195

186196
protected async _nextState(state: State, options: InteractiveEditorRunOptions | undefined): Promise<void> {
187-
this._logService.trace('[IE] setState to ', state);
197+
this._log('setState to ', state);
188198
const nextState = await this[state](options);
189199
if (nextState) {
190200
await this._nextState(nextState, options);
@@ -200,7 +210,7 @@ export class InteractiveEditorController implements IEditorContribution {
200210
if (!session) {
201211
const createSessionCts = new CancellationTokenSource();
202212
const msgListener = Event.once(this._messages.event)(m => {
203-
this._logService.trace('[IE](state=_createSession) message received', m);
213+
this._log('state=_createSession) message received', m);
204214
createSessionCts.cancel();
205215
});
206216

@@ -261,11 +271,12 @@ export class InteractiveEditorController implements IEditorContribution {
261271
this._zone.widget.updateInfo(this._activeSession.session.message ?? localize('welcome.1', "AI-generated code may be incorrect"));
262272
this._zone.show(this._activeSession.wholeRange.getEndPosition());
263273

264-
this._sessionStore.add(this._editor.onDidChangeModel(() => {
265-
this._messages.fire(this._activeSession?.lastExchange
274+
this._sessionStore.add(this._editor.onDidChangeModel((e) => {
275+
const msg = this._activeSession?.lastExchange
266276
? Message.PAUSE_SESSION // pause when switching models/tabs and when having a previous exchange
267-
: Message.CANCEL_SESSION
268-
);
277+
: Message.CANCEL_SESSION;
278+
this._log('model changed, pause or cancel session', msg, e);
279+
this._messages.fire(msg);
269280
}));
270281

271282
this._sessionStore.add(this._editor.onDidChangeModelContent(e => {
@@ -282,7 +293,7 @@ export class InteractiveEditorController implements IEditorContribution {
282293
this._activeSession!.recordExternalEditOccurred(editIsOutsideOfWholeRange);
283294

284295
if (editIsOutsideOfWholeRange) {
285-
this._logService.info('[IE] text changed outside of whole range, FINISH session');
296+
this._log('text changed outside of whole range, FINISH session');
286297
this._finishExistingSession();
287298
}
288299
}));
@@ -363,7 +374,7 @@ export class InteractiveEditorController implements IEditorContribution {
363374
} else {
364375
const barrier = new Barrier();
365376
const msgListener = Event.once(this._messages.event)(m => {
366-
this._logService.trace('[IE](state=_waitForInput) message received', m);
377+
this._log('state=_waitForInput) message received', m);
367378
message = m;
368379
barrier.open();
369380
});
@@ -397,7 +408,7 @@ export class InteractiveEditorController implements IEditorContribution {
397408

398409
const refer = this._activeSession.session.slashCommands?.some(value => value.refer && input!.startsWith(`/${value.command}`));
399410
if (refer) {
400-
this._logService.info('[IE] seeing refer command, continuing outside editor', this._activeSession.provider.debugName);
411+
this._log('[IE] seeing refer command, continuing outside editor', this._activeSession.provider.debugName);
401412
this._editor.setSelection(this._activeSession.wholeRange);
402413
this._instaService.invokeFunction(sendRequest, input);
403414

@@ -421,7 +432,7 @@ export class InteractiveEditorController implements IEditorContribution {
421432

422433
let message = Message.NONE;
423434
const msgListener = Event.once(this._messages.event)(m => {
424-
this._logService.trace('[IE](state=_makeRequest) message received', m);
435+
this._log('state=_makeRequest) message received', m);
425436
message = m;
426437
requestCts.cancel();
427438
});
@@ -438,7 +449,7 @@ export class InteractiveEditorController implements IEditorContribution {
438449
attempt: 0,
439450
};
440451
const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, requestCts.token);
441-
this._logService.trace('[IE] request started', this._activeSession.provider.debugName, this._activeSession.session, request);
452+
this._log('request started', this._activeSession.provider.debugName, this._activeSession.session, request);
442453

443454
let response: EditResponse | MarkdownResponse | ErrorResponse | EmptyResponse;
444455
let reply: IInteractiveEditorResponse | null | undefined;
@@ -463,7 +474,7 @@ export class InteractiveEditorController implements IEditorContribution {
463474
this._ctxHasActiveRequest.set(false);
464475
this._zone.widget.updateProgress(false);
465476
this._zone.widget.updateInfo('');
466-
this._logService.trace('[IE] request took', sw.elapsed(), this._activeSession.provider.debugName);
477+
this._log('request took', sw.elapsed(), this._activeSession.provider.debugName);
467478

468479
}
469480

@@ -497,7 +508,7 @@ export class InteractiveEditorController implements IEditorContribution {
497508
}
498509
const moreMinimalEdits = (await this._editorWorkerService.computeHumanReadableDiff(this._activeSession.textModelN.uri, response.localEdits));
499510
const editOperations = (moreMinimalEdits ?? response.localEdits).map(edit => EditOperation.replace(Range.lift(edit.range), edit.text));
500-
this._logService.trace('[IE] edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, response.localEdits, moreMinimalEdits);
511+
this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, response.localEdits, moreMinimalEdits);
501512

502513
const textModelNplus1 = this._modelService.createModel(createTextBufferFactoryFromSnapshot(this._activeSession.textModelN.createSnapshot()), null, undefined, true);
503514
textModelNplus1.applyEdits(editOperations);
@@ -682,8 +693,8 @@ export class InteractiveEditorController implements IEditorContribution {
682693
await strategy?.apply();
683694
} catch (err) {
684695
this._dialogService.error(localize('err.apply', "Failed to apply changes.", toErrorMessage(err)));
685-
this._logService.error('[IE] FAILED to apply changes');
686-
this._logService.error(err);
696+
this._log('FAILED to apply changes');
697+
this._log(err);
687698
}
688699
strategy?.dispose();
689700
this._messages.fire(Message.ACCEPT_SESSION);
@@ -702,8 +713,8 @@ export class InteractiveEditorController implements IEditorContribution {
702713
await strategy?.cancel();
703714
} catch (err) {
704715
this._dialogService.error(localize('err.discard', "Failed to discard changes.", toErrorMessage(err)));
705-
this._logService.error('[IE] FAILED to discard changes');
706-
this._logService.error(err);
716+
this._log('FAILED to discard changes');
717+
this._log(err);
707718
}
708719
strategy?.dispose();
709720
this._messages.fire(Message.CANCEL_SESSION);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { illegalState } from 'vs/base/common/errors';
7+
import { Schemas } from 'vs/base/common/network';
8+
import { isEqual } from 'vs/base/common/resources';
9+
import { IInteractiveEditorSessionService } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorSession';
10+
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService';
11+
import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon';
12+
13+
export class InteractiveEditorNotebookContribution {
14+
15+
constructor(
16+
@IInteractiveEditorSessionService sessionService: IInteractiveEditorSessionService,
17+
@INotebookEditorService notebookEditorService: INotebookEditorService,
18+
) {
19+
20+
sessionService.registerSessionKeyComputer(Schemas.vscodeNotebookCell, {
21+
getComparisonKey: (_editor, uri) => {
22+
const data = CellUri.parse(uri);
23+
if (!data) {
24+
throw illegalState('Expected notebook');
25+
}
26+
for (const editor of notebookEditorService.listNotebookEditors()) {
27+
if (isEqual(editor.textModel?.uri, data.notebook)) {
28+
return `<notebook>${editor.getId()}#${uri}`;
29+
}
30+
}
31+
throw illegalState('Expected notebook');
32+
}
33+
});
34+
}
35+
}

src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorSession.ts

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { EditMode, IInteractiveEditorSessionProvider, IInteractiveEditorSession,
1313
import { IRange, Range } from 'vs/editor/common/core/range';
1414
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
1515
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
16-
import { ResourceMap } from 'vs/base/common/map';
1716
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
1817
import { IModelService } from 'vs/editor/common/services/model';
1918
import { ITextModelService } from 'vs/editor/common/services/resolverService';
@@ -266,6 +265,10 @@ export class EditResponse {
266265
}
267266
}
268267

268+
export interface ISessionKeyComputer {
269+
getComparisonKey(editor: ICodeEditor, uri: URI): string;
270+
}
271+
269272
export const IInteractiveEditorSessionService = createDecorator<IInteractiveEditorSessionService>('IInteractiveEditorSessionService');
270273

271274
export interface IInteractiveEditorSessionService {
@@ -277,6 +280,8 @@ export interface IInteractiveEditorSessionService {
277280

278281
releaseSession(session: Session): void;
279282

283+
registerSessionKeyComputer(scheme: string, value: ISessionKeyComputer): IDisposable;
284+
280285
//
281286

282287
recordings(): readonly Recording[];
@@ -291,7 +296,8 @@ export class InteractiveEditorSessionService implements IInteractiveEditorSessio
291296

292297
declare _serviceBrand: undefined;
293298

294-
private readonly _sessions = new Map<ICodeEditor, ResourceMap<SessionData>>();
299+
private readonly _sessions = new Map<string, SessionData>();
300+
private readonly _keyComputers = new Map<string, ISessionKeyComputer>();
295301
private _recordings: Recording[] = [];
296302

297303
constructor(
@@ -360,35 +366,27 @@ export class InteractiveEditorSessionService implements IInteractiveEditorSessio
360366

361367
const session = new Session(options.editMode, editor, textModel0, textModel, provider, raw, wholeRangeDecorationId);
362368

363-
// store: editor -> uri -> session
364-
let map = this._sessions.get(editor);
365-
if (!map) {
366-
map = new ResourceMap<SessionData>();
367-
this._sessions.set(editor, map);
369+
// store: key -> session
370+
const key = this._key(editor, textModel.uri);
371+
if (this._sessions.has(key)) {
372+
store.dispose();
373+
throw new Error(`Session already stored for ${key}`);
368374
}
369-
if (map.has(textModel.uri)) {
370-
throw new Error(`Session already stored for ${textModel.uri}`);
371-
}
372-
map.set(textModel.uri, { session, store });
375+
this._sessions.set(key, { session, store });
373376
return session;
374377
}
375378

376379
releaseSession(session: Session): void {
377380

378-
const { editor, textModelN } = session;
381+
const { editor } = session;
379382

380383
// cleanup
381-
const map = this._sessions.get(editor);
382-
if (map) {
383-
const data = map.get(textModelN.uri);
384-
if (data) {
385-
data.store.dispose();
386-
data.session.session.dispose?.();
387-
388-
map.delete(textModelN.uri);
389-
}
390-
if (map.size === 0) {
391-
this._sessions.delete(editor);
384+
for (const [key, value] of this._sessions) {
385+
if (value.session === session) {
386+
value.store.dispose();
387+
this._sessions.delete(key);
388+
this._logService.trace(`[IE] did RELEASED session for ${editor.getId()}, ${session.provider.debugName}`);
389+
break;
392390
}
393391
}
394392

@@ -403,7 +401,21 @@ export class InteractiveEditorSessionService implements IInteractiveEditorSessio
403401
}
404402

405403
getSession(editor: ICodeEditor, uri: URI): Session | undefined {
406-
return this._sessions.get(editor)?.get(uri)?.session;
404+
const key = this._key(editor, uri);
405+
return this._sessions.get(key)?.session;
406+
}
407+
408+
private _key(editor: ICodeEditor, uri: URI): string {
409+
const item = this._keyComputers.get(uri.scheme);
410+
return item
411+
? item.getComparisonKey(editor, uri)
412+
: `${editor.getId()}@${uri.toString()}`;
413+
414+
}
415+
416+
registerSessionKeyComputer(scheme: string, value: ISessionKeyComputer): IDisposable {
417+
this._keyComputers.set(scheme, value);
418+
return toDisposable(() => this._keyComputers.delete(scheme));
407419
}
408420

409421
// --- debug

0 commit comments

Comments
 (0)