Skip to content

Commit 4752734

Browse files
authored
Merge branch 'main' into tyriar/258512__259342__259339
2 parents cda153f + 2eb834d commit 4752734

18 files changed

+259
-65
lines changed

src/vs/workbench/api/common/extHost.api.impl.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1859,6 +1859,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
18591859
ChatPrepareToolInvocationPart: extHostTypes.ChatPrepareToolInvocationPart,
18601860
ChatResponseMultiDiffPart: extHostTypes.ChatResponseMultiDiffPart,
18611861
ChatResponseReferencePartStatusKind: extHostTypes.ChatResponseReferencePartStatusKind,
1862+
ChatResponseClearToPreviousToolInvocationReason: extHostTypes.ChatResponseClearToPreviousToolInvocationReason,
18621863
ChatRequestTurn: extHostTypes.ChatRequestTurn,
18631864
ChatRequestTurn2: extHostTypes.ChatRequestTurn,
18641865
ChatResponseTurn: extHostTypes.ChatResponseTurn,

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import { IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from '../../c
5757
import { ICodeMapperRequest, ICodeMapperResult } from '../../contrib/chat/common/chatCodeMapperService.js';
5858
import { IChatRelatedFile, IChatRelatedFileProviderMetadata as IChatRelatedFilesProviderMetadata, IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js';
5959
import { IChatProgressHistoryResponseContent } from '../../contrib/chat/common/chatModel.js';
60-
import { IChatContentInlineReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js';
60+
import { IChatContentInlineReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction, ChatResponseClearToPreviousToolInvocationReason } from '../../contrib/chat/common/chatService.js';
6161
import { IChatSessionItem } from '../../contrib/chat/common/chatSessionsService.js';
6262
import { IChatRequestVariableValue } from '../../contrib/chat/common/chatVariables.js';
6363
import { ChatAgentLocation } from '../../contrib/chat/common/constants.js';
@@ -1438,7 +1438,8 @@ export type IDocumentContextDto = {
14381438
export type IChatProgressDto =
14391439
| Dto<Exclude<IChatProgress, IChatTask | IChatNotebookEdit>>
14401440
| IChatTaskDto
1441-
| IChatNotebookEditDto;
1441+
| IChatNotebookEditDto
1442+
| IChatResponseClearToPreviousToolInvocationDto;
14421443

14431444
export interface ExtHostUrlsShape {
14441445
$handleExternalUri(handle: number, uri: UriComponents): Promise<void>;
@@ -2193,6 +2194,11 @@ export interface IChatNotebookEditDto {
21932194
done?: boolean;
21942195
}
21952196

2197+
export interface IChatResponseClearToPreviousToolInvocationDto {
2198+
kind: 'clearToPreviousToolInvocation';
2199+
reason: ChatResponseClearToPreviousToolInvocationReason;
2200+
}
2201+
21962202
export type ICellEditOperationDto =
21972203
notebookCommon.ICellMetadataEdit
21982204
| notebookCommon.IDocumentMetadataEdit

src/vs/workbench/api/common/extHostChatAgents2.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ export class ChatAgentResponseStream {
135135
};
136136

137137
this._apiObject = Object.freeze<vscode.ChatResponseStream>({
138+
clearToPreviousToolInvocation(reason) {
139+
throwIfDone(this.markdown);
140+
send({ kind: 'clearToPreviousToolInvocation', reason: reason });
141+
return this;
142+
},
138143
markdown(value) {
139144
throwIfDone(this.markdown);
140145
const part = new extHostTypes.ChatResponseMarkdownPart(value);

src/vs/workbench/api/common/extHostTypes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4848,6 +4848,12 @@ export enum ChatResponseReferencePartStatusKind {
48484848
Omitted = 3
48494849
}
48504850

4851+
export enum ChatResponseClearToPreviousToolInvocationReason {
4852+
NoReason = 0,
4853+
FilteredContentRetry = 1,
4854+
CopyrightContentRetry = 2,
4855+
}
4856+
48514857
export class ChatRequestEditorData implements vscode.ChatRequestEditorData {
48524858
constructor(
48534859
readonly document: vscode.TextDocument,

src/vs/workbench/contrib/chat/browser/chatContentParts/chatChangesSummaryPart.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { ChatTreeItem } from '../chat.js';
1212
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
1313
import { IChatChangesSummary as IChatFileChangesSummary, IChatService } from '../../common/chatService.js';
1414
import { IEditorService } from '../../../../services/editor/common/editorService.js';
15-
import { IEditSessionEntryDiff } from '../../common/chatEditingService.js';
15+
import { IChatEditingSession, IEditSessionEntryDiff } from '../../common/chatEditingService.js';
1616
import { WorkbenchList } from '../../../../../platform/list/browser/listService.js';
1717
import { ButtonWithIcon } from '../../../../../base/browser/ui/button/button.js';
1818
import { Codicon } from '../../../../../base/common/codicons.js';
@@ -24,7 +24,7 @@ import { IListRenderer, IListVirtualDelegate } from '../../../../../base/browser
2424
import { FileKind } from '../../../../../platform/files/common/files.js';
2525
import { createFileIconThemableTreeContainerScope } from '../../../files/browser/views/explorerView.js';
2626
import { IThemeService } from '../../../../../platform/theme/common/themeService.js';
27-
import { autorun, derived, IObservableWithChange } from '../../../../../base/common/observable.js';
27+
import { autorun, derived, IObservable, IObservableWithChange } from '../../../../../base/common/observable.js';
2828
import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js';
2929
import { MultiDiffEditorItem } from '../../../multiDiffEditor/browser/multiDiffSourceResolverService.js';
3030
import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js';
@@ -40,6 +40,8 @@ export class ChatCheckpointFileChangesSummaryContentPart extends Disposable impl
4040
private readonly _onDidChangeHeight = this._register(new Emitter<void>());
4141
public readonly onDidChangeHeight = this._onDidChangeHeight.event;
4242

43+
private readonly diffsBetweenRequests = new Map<string, IObservable<IEditSessionEntryDiff | undefined>>();
44+
4345
private fileChanges: readonly IChatFileChangesSummary[];
4446
private fileChangesDiffsObservable: IObservableWithChange<Map<string, IEditSessionEntryDiff>, void>;
4547

@@ -74,6 +76,8 @@ export class ChatCheckpointFileChangesSummaryContentPart extends Disposable impl
7476
private computeFileChangesDiffs(context: IChatContentPartRenderContext, changes: readonly IChatFileChangesSummary[]): IObservableWithChange<Map<string, IEditSessionEntryDiff>, void> {
7577
return derived((r) => {
7678
const fileChangesDiffs = new Map<string, IEditSessionEntryDiff>();
79+
const firstRequestId = changes[0].requestId;
80+
const lastRequestId = changes[changes.length - 1].requestId;
7781
for (const change of changes) {
7882
const sessionId = change.sessionId;
7983
const session = this.chatService.getSession(sessionId);
@@ -84,28 +88,26 @@ export class ChatCheckpointFileChangesSummaryContentPart extends Disposable impl
8488
if (!editSession) {
8589
continue;
8690
}
87-
const uri = change.reference;
88-
const modifiedEntry = editSession.getEntry(uri);
89-
if (!modifiedEntry) {
91+
const diff = this.getCachedEntryDiffBetweenRequests(editSession, change.reference, firstRequestId, lastRequestId)?.read(r);
92+
if (!diff) {
9093
continue;
9194
}
92-
const requestId = change.requestId;
93-
const undoStops = context.content.filter(e => e.kind === 'undoStop');
94-
95-
for (let i = undoStops.length - 1; i >= 0; i--) {
96-
const modifiedUri = modifiedEntry.modifiedURI;
97-
const undoStopID = undoStops[i].id;
98-
const diff = editSession.getEntryDiffBetweenStops(modifiedUri, requestId, undoStopID)?.read(r);
99-
if (!diff) {
100-
continue;
101-
}
102-
fileChangesDiffs.set(this.changeID(change), diff);
103-
}
95+
fileChangesDiffs.set(this.changeID(change), diff);
10496
}
10597
return fileChangesDiffs;
10698
});
10799
}
108100

101+
public getCachedEntryDiffBetweenRequests(editSession: IChatEditingSession, uri: URI, startRequestId: string, stopRequestId: string): IObservable<IEditSessionEntryDiff | undefined> | undefined {
102+
const key = `${uri}\0${startRequestId}\0${stopRequestId}`;
103+
let observable = this.diffsBetweenRequests.get(key);
104+
if (!observable) {
105+
observable = editSession.getEntryDiffBetweenRequests(uri, startRequestId, stopRequestId);
106+
this.diffsBetweenRequests.set(key, observable);
107+
}
108+
return observable;
109+
}
110+
109111
private renderHeader(container: HTMLElement): IDisposable {
110112
const viewListButtonContainer = container.appendChild($('.chat-file-changes-label'));
111113
const viewListButton = new ButtonWithIcon(viewListButtonContainer, {});
@@ -169,11 +171,13 @@ export class ChatCheckpointFileChangesSummaryContentPart extends Disposable impl
169171
private renderFilesList(container: HTMLElement): IDisposable {
170172
const store = new DisposableStore();
171173
this.list = store.add(this.instantiationService.createInstance(CollapsibleChangesSummaryListPool)).get();
174+
const listNode = this.list.getHTMLElement();
172175
const itemsShown = Math.min(this.fileChanges.length, this.MAX_ITEMS_SHOWN);
173176
const height = itemsShown * this.ELEMENT_HEIGHT;
174177
this.list.layout(height);
178+
listNode.style.height = height + 'px';
175179
this.updateList(this.fileChanges, this.fileChangesDiffsObservable.get());
176-
container.appendChild(this.list.getHTMLElement().parentElement!);
180+
container.appendChild(listNode.parentElement!);
177181

178182
store.add(this.list.onDidOpen((item) => {
179183
const element = item.element;

src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -969,17 +969,31 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie
969969
if (!cell) {
970970
throw new Error('Cell not found');
971971
}
972-
const model = this.cellTextModelMap.get(cell.uri) || this._register(await this.textModelService.createModelReference(cell.uri)).object.textEditorModel;
973-
this.cellTextModelMap.set(cell.uri, model);
974-
return model;
972+
const model = this.cellTextModelMap.get(cell.uri);
973+
if (model) {
974+
this.cellTextModelMap.set(cell.uri, model);
975+
return model;
976+
} else {
977+
const textEditorModel = await this.textModelService.createModelReference(cell.uri);
978+
if (this._store.isDisposed) {
979+
textEditorModel.dispose();
980+
} else {
981+
this._register(textEditorModel);
982+
}
983+
const model = textEditorModel.object.textEditorModel;
984+
this.cellTextModelMap.set(cell.uri, model);
985+
return model;
986+
}
975987
}
976988

977989
getOrCreateModifiedTextFileEntryForCell(cell: NotebookCellTextModel, modifiedCellModel: ITextModel, originalCellModel: ITextModel): ChatEditingNotebookCellEntry | undefined {
978990
let cellEntry = this.cellEntryMap.get(cell.uri);
979991
if (cellEntry) {
980992
return cellEntry;
981993
}
982-
994+
if (this._store.isDisposed) {
995+
return;
996+
}
983997
const disposables = new DisposableStore();
984998
cellEntry = this._register(this._instantiationService.createInstance(ChatEditingNotebookCellEntry, this.modifiedResourceRef.object.resource, cell, modifiedCellModel, originalCellModel, disposables));
985999
this.cellEntryMap.set(cell.uri, cellEntry);

src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
214214
return this._timeline.getEntryDiffBetweenStops(uri, requestId, stopId);
215215
}
216216

217+
public getEntryDiffBetweenRequests(uri: URI, startRequestId: string, stopRequestId: string) {
218+
return this._timeline.getEntryDiffBetweenRequests(uri, startRequestId, stopRequestId);
219+
}
220+
217221
public createSnapshot(requestId: string, undoStop: string | undefined, makeEmpty = undoStop !== undefined): void {
218222
this._timeline.pushSnapshot(
219223
requestId,

src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTimeline.ts

Lines changed: 87 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66

77

88
import { equals as arraysEqual, binarySearch2 } from '../../../../../base/common/arrays.js';
9-
import { equals as objectsEqual } from '../../../../../base/common/objects.js';
109
import { findLast } from '../../../../../base/common/arraysFind.js';
1110
import { Iterable } from '../../../../../base/common/iterator.js';
1211
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
1312
import { ResourceMap } from '../../../../../base/common/map.js';
13+
import { equals as objectsEqual } from '../../../../../base/common/objects.js';
1414
import { derived, derivedOpts, IObservable, ITransaction, ObservablePromise, observableValue, transaction } from '../../../../../base/common/observable.js';
1515
import { isEqual } from '../../../../../base/common/resources.js';
1616
import { URI } from '../../../../../base/common/uri.js';
@@ -338,29 +338,7 @@ export class ChatEditingTimeline {
338338

339339
}
340340
const ignoreTrimWhitespace = this._ignoreTrimWhitespaceObservable.read(reader);
341-
const promise = this._editorWorkerService.computeDiff(
342-
refs[0].object.textEditorModel.uri,
343-
refs[1].object.textEditorModel.uri,
344-
{ ignoreTrimWhitespace, computeMoves: false, maxComputationTimeMs: 3000 },
345-
'advanced'
346-
).then((diff): IEditSessionEntryDiff => {
347-
const entryDiff: IEditSessionEntryDiff = {
348-
originalURI: refs[0].object.textEditorModel.uri,
349-
modifiedURI: refs[1].object.textEditorModel.uri,
350-
identical: !!diff?.identical,
351-
quitEarly: !diff || diff.quitEarly,
352-
added: 0,
353-
removed: 0,
354-
};
355-
if (diff) {
356-
for (const change of diff.changes) {
357-
entryDiff.removed += change.original.endLineNumberExclusive - change.original.startLineNumber;
358-
entryDiff.added += change.modified.endLineNumberExclusive - change.modified.startLineNumber;
359-
}
360-
}
361-
362-
return entryDiff;
363-
});
341+
const promise = this._computeDiff(refs[0].object.textEditorModel.uri, refs[1].object.textEditorModel.uri, ignoreTrimWhitespace);
364342

365343
return new ObservablePromise(promise);
366344
});
@@ -418,6 +396,91 @@ export class ChatEditingTimeline {
418396
return observable;
419397
}
420398
}
399+
400+
public getEntryDiffBetweenRequests(uri: URI, startRequestId: string, stopRequestId: string): IObservable<IEditSessionEntryDiff | undefined> {
401+
const snapshotUris = derivedOpts<[URI | undefined, URI | undefined]>(
402+
{ equalsFn: (a, b) => arraysEqual(a, b, isEqual) },
403+
reader => {
404+
const history = this._linearHistory.read(reader);
405+
const firstSnapshotUri = this._getFirstSnapshotForUriAfterRequest(history, uri, startRequestId, true);
406+
const lastSnapshotUri = this._getFirstSnapshotForUriAfterRequest(history, uri, stopRequestId, false);
407+
return [firstSnapshotUri, lastSnapshotUri];
408+
},
409+
);
410+
const modelRefs = derived((reader) => {
411+
const snapshots = snapshotUris.read(reader);
412+
const firstSnapshotUri = snapshots[0];
413+
const lastSnapshotUri = snapshots[1];
414+
if (!firstSnapshotUri || !lastSnapshotUri) {
415+
return;
416+
}
417+
const store = new DisposableStore();
418+
reader.store.add(store);
419+
const referencesPromise = Promise.all([firstSnapshotUri, lastSnapshotUri].map(u => this._textModelService.createModelReference(u))).then(refs => {
420+
if (store.isDisposed) {
421+
refs.forEach(ref => ref.dispose());
422+
} else {
423+
refs.forEach(ref => store.add(ref));
424+
}
425+
return refs;
426+
});
427+
return new ObservablePromise(referencesPromise);
428+
});
429+
const diff = derived((reader): ObservablePromise<IEditSessionEntryDiff> | undefined => {
430+
const references = modelRefs.read(reader)?.promiseResult.read(reader);
431+
const refs = references?.data;
432+
if (!refs) {
433+
return;
434+
}
435+
const ignoreTrimWhitespace = this._ignoreTrimWhitespaceObservable.read(reader);
436+
const promise = this._computeDiff(refs[0].object.textEditorModel.uri, refs[1].object.textEditorModel.uri, ignoreTrimWhitespace);
437+
return new ObservablePromise(promise);
438+
});
439+
return derived(reader => {
440+
return diff.read(reader)?.promiseResult.read(reader)?.data || undefined;
441+
});
442+
}
443+
444+
private _computeDiff(originalUri: URI, modifiedUri: URI, ignoreTrimWhitespace: boolean): Promise<IEditSessionEntryDiff> {
445+
return this._editorWorkerService.computeDiff(
446+
originalUri,
447+
modifiedUri,
448+
{ ignoreTrimWhitespace, computeMoves: false, maxComputationTimeMs: 3000 },
449+
'advanced'
450+
).then((diff): IEditSessionEntryDiff => {
451+
const entryDiff: IEditSessionEntryDiff = {
452+
originalURI: originalUri,
453+
modifiedURI: modifiedUri,
454+
identical: !!diff?.identical,
455+
quitEarly: !diff || diff.quitEarly,
456+
added: 0,
457+
removed: 0,
458+
};
459+
if (diff) {
460+
for (const change of diff.changes) {
461+
entryDiff.removed += change.original.endLineNumberExclusive - change.original.startLineNumber;
462+
entryDiff.added += change.modified.endLineNumberExclusive - change.modified.startLineNumber;
463+
}
464+
}
465+
return entryDiff;
466+
});
467+
}
468+
469+
private _getFirstSnapshotForUriAfterRequest(history: readonly IChatEditingSessionSnapshot[], uri: URI, requestId: string, inclusive: boolean): URI | undefined {
470+
const requestIndex = history.findIndex(s => s.requestId === requestId);
471+
if (requestIndex === -1) { return undefined; }
472+
const processedIndex = requestIndex + (inclusive ? 0 : 1);
473+
for (let i = processedIndex; i < history.length; i++) {
474+
const snapshot = history[i];
475+
for (const stop of snapshot.stops) {
476+
const entry = stop.entries.get(uri);
477+
if (entry) {
478+
return entry.snapshotUri;
479+
}
480+
}
481+
}
482+
return uri;
483+
}
421484
}
422485

423486
function stopProvidesNewData(origin: IChatEditingSessionStop, target: IChatEditingSessionStop) {

src/vs/workbench/contrib/chat/common/chatEditingService.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ export interface IChatEditingSession extends IDisposable {
143143
*/
144144
getEntryDiffBetweenStops(uri: URI, requestId: string | undefined, stopId: string | undefined): IObservable<IEditSessionEntryDiff | undefined> | undefined;
145145

146+
/**
147+
* Gets the document diff of a change made to a URI between one request to another one.
148+
* @returns The observable or undefined if there is no diff between the requests.
149+
*/
150+
getEntryDiffBetweenRequests(uri: URI, startRequestIs: string, stopRequestId: string): IObservable<IEditSessionEntryDiff | undefined>;
151+
146152
readonly canUndo: IObservable<boolean>;
147153
readonly canRedo: IObservable<boolean>;
148154
undoInteraction(): Promise<void>;

0 commit comments

Comments
 (0)