Skip to content

Commit 0a2707c

Browse files
authored
edits: add session-level diffs for file stats (#279254)
* edits: add session-level diffs for file stats * rm debug * dfgkdfghnh
1 parent 9ae8955 commit 0a2707c

File tree

4 files changed

+93
-11
lines changed

4 files changed

+93
-11
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { VSBuffer } from '../../../../../base/common/buffer.js';
77
import { IDisposable } from '../../../../../base/common/lifecycle.js';
88
import { IObservable, ITransaction } from '../../../../../base/common/observable.js';
99
import { URI } from '../../../../../base/common/uri.js';
10-
import { IEditSessionEntryDiff } from '../../common/chatEditingService.js';
10+
import { IEditSessionDiffStats, IEditSessionEntryDiff } from '../../common/chatEditingService.js';
1111
import { IChatRequestDisablement } from '../../common/chatModel.js';
1212
import { FileOperation, IChatEditingTimelineState, IFileBaseline } from './chatEditingOperations.js';
1313

@@ -46,4 +46,6 @@ export interface IChatEditingCheckpointTimeline {
4646
// Diffing
4747
getEntryDiffBetweenStops(uri: URI, requestId: string | undefined, stopId: string | undefined): IObservable<IEditSessionEntryDiff | undefined> | undefined;
4848
getEntryDiffBetweenRequests(uri: URI, startRequestId: string, stopRequestId: string): IObservable<IEditSessionEntryDiff | undefined>;
49+
getDiffsForFilesInSession(): IObservable<readonly IEditSessionEntryDiff[]>;
50+
getDiffForSession(): IObservable<IEditSessionDiffStats>;
4951
}

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

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import { assertNever } from '../../../../../base/common/assert.js';
99
import { ThrottledDelayer } from '../../../../../base/common/async.js';
1010
import { Event } from '../../../../../base/common/event.js';
1111
import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';
12-
import { ResourceSet } from '../../../../../base/common/map.js';
12+
import { mapsStrictEqualIgnoreOrder, ResourceMap, ResourceSet } from '../../../../../base/common/map.js';
1313
import { equals as objectsEqual } from '../../../../../base/common/objects.js';
14-
import { derived, derivedOpts, IObservable, IReader, ITransaction, ObservablePromise, observableSignalFromEvent, observableValue, observableValueOpts, transaction } from '../../../../../base/common/observable.js';
14+
import { constObservable, derived, derivedOpts, IObservable, IReader, ITransaction, ObservablePromise, observableSignalFromEvent, observableValue, observableValueOpts, transaction } from '../../../../../base/common/observable.js';
1515
import { isEqual } from '../../../../../base/common/resources.js';
16-
import { Mutable } from '../../../../../base/common/types.js';
16+
import { isDefined, Mutable } from '../../../../../base/common/types.js';
1717
import { URI } from '../../../../../base/common/uri.js';
1818
import { generateUuid } from '../../../../../base/common/uuid.js';
1919
import { TextEdit } from '../../../../../editor/common/languages.js';
@@ -26,7 +26,7 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com
2626
import { CellEditType, CellUri, INotebookTextModel } from '../../../notebook/common/notebookCommon.js';
2727
import { INotebookEditorModelResolverService } from '../../../notebook/common/notebookEditorModelResolverService.js';
2828
import { INotebookService } from '../../../notebook/common/notebookService.js';
29-
import { IEditSessionEntryDiff, IModifiedEntryTelemetryInfo } from '../../common/chatEditingService.js';
29+
import { IEditSessionDiffStats, IEditSessionEntryDiff, IModifiedEntryTelemetryInfo } from '../../common/chatEditingService.js';
3030
import { IChatRequestDisablement } from '../../common/chatModel.js';
3131
import { IChatEditingCheckpointTimeline } from './chatEditingCheckpointTimeline.js';
3232
import { FileOperation, FileOperationType, IChatEditingTimelineState, ICheckpoint, IFileBaseline, IReconstructedFileExistsState, IReconstructedFileNotExistsState, IReconstructedFileState } from './chatEditingOperations.js';
@@ -832,4 +832,62 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint
832832
return entryDiff;
833833
});
834834
}
835+
836+
public getDiffsForFilesInSession(): IObservable<readonly IEditSessionEntryDiff[]> {
837+
const startEpochs = derivedOpts<ResourceMap<number>>({ equalsFn: mapsStrictEqualIgnoreOrder }, reader => {
838+
const uris = new ResourceMap<number>();
839+
for (const baseline of this._fileBaselines.values()) {
840+
uris.set(baseline.uri, Math.min(baseline.epoch, uris.get(baseline.uri) ?? Number.MAX_SAFE_INTEGER));
841+
}
842+
for (const operation of this._operations.read(reader)) {
843+
if (operation.type === FileOperationType.Create) {
844+
uris.set(operation.uri, 0);
845+
}
846+
}
847+
848+
return uris;
849+
});
850+
851+
// URIs are never removed from the set and we never adjust baselines backwards
852+
// (history is immutable) so we can easily cache to avoid regenerating diffs when new files are added
853+
const prevDiffs = new ResourceMap<IObservable<IEditSessionEntryDiff | undefined>>();
854+
855+
const perFileDiffs = derived(this, reader => {
856+
const checkpoints = this._checkpoints.read(reader);
857+
const firstCheckpoint = checkpoints[0];
858+
if (!firstCheckpoint) {
859+
return [];
860+
}
861+
862+
const uris = startEpochs.read(reader);
863+
const diffs: IObservable<IEditSessionEntryDiff | undefined>[] = [];
864+
865+
for (const [uri, epoch] of uris) {
866+
const obs = prevDiffs.get(uri) ?? this._getEntryDiffBetweenEpochs(uri,
867+
constObservable({ start: checkpoints.findLast(cp => cp.epoch <= epoch) || firstCheckpoint, end: undefined }));
868+
prevDiffs.set(uri, obs);
869+
diffs.push(obs);
870+
}
871+
872+
return diffs;
873+
});
874+
875+
return perFileDiffs.map((diffs, reader) => {
876+
return diffs.flatMap(d => d.read(reader)).filter(isDefined);
877+
});
878+
}
879+
880+
public getDiffForSession(): IObservable<IEditSessionDiffStats> {
881+
const fileDiffs = this.getDiffsForFilesInSession();
882+
return derived(reader => {
883+
const diffs = fileDiffs.read(reader);
884+
let added = 0;
885+
let removed = 0;
886+
for (const diff of diffs) {
887+
added += diff.added;
888+
removed += diff.removed;
889+
}
890+
return { added, removed };
891+
});
892+
}
835893
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
198198
chatSessionResource,
199199
this._getTimelineDelegate(),
200200
);
201+
201202
this.canRedo = this._timeline.canRedo.map((hasHistory, reader) =>
202203
hasHistory && this._state.read(reader) === ChatEditingSessionState.Idle);
203204
this.canUndo = this._timeline.canUndo.map((hasHistory, reader) =>
@@ -316,6 +317,14 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
316317
return this._timeline.getEntryDiffBetweenRequests(uri, startRequestId, stopRequestId);
317318
}
318319

320+
public getDiffsForFilesInSession() {
321+
return this._timeline.getDiffsForFilesInSession();
322+
}
323+
324+
public getDiffForSession() {
325+
return this._timeline.getDiffForSession();
326+
}
327+
319328
public createSnapshot(requestId: string, undoStop: string | undefined): void {
320329
const label = undoStop ? `Request ${requestId} - Stop ${undoStop}` : `Request ${requestId}`;
321330
this._timeline.createCheckpoint(requestId, undoStop, label);

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,17 @@ export interface IChatEditingSession extends IDisposable {
172172
*/
173173
getEntryDiffBetweenRequests(uri: URI, startRequestIs: string, stopRequestId: string): IObservable<IEditSessionEntryDiff | undefined>;
174174

175+
/**
176+
* Gets the diff of each file modified in this session, comparing the initial
177+
* baseline to the current state.
178+
*/
179+
getDiffsForFilesInSession(): IObservable<readonly IEditSessionEntryDiff[]>;
180+
181+
/**
182+
* Gets the aggregated diff stats for all files modified in this session.
183+
*/
184+
getDiffForSession(): IObservable<IEditSessionDiffStats>;
185+
175186
readonly canUndo: IObservable<boolean>;
176187
readonly canRedo: IObservable<boolean>;
177188
undoInteraction(): Promise<void>;
@@ -190,7 +201,14 @@ export function chatEditingSessionIsReady(session: IChatEditingSession): Promise
190201
});
191202
}
192203

193-
export interface IEditSessionEntryDiff {
204+
export interface IEditSessionDiffStats {
205+
/** Added data (e.g. line numbers) to show in the UI */
206+
added: number;
207+
/** Removed data (e.g. line numbers) to show in the UI */
208+
removed: number;
209+
}
210+
211+
export interface IEditSessionEntryDiff extends IEditSessionDiffStats {
194212
/** LHS and RHS of a diff editor, if opened: */
195213
originalURI: URI;
196214
modifiedURI: URI;
@@ -201,11 +219,6 @@ export interface IEditSessionEntryDiff {
201219

202220
/** True if nothing else will be added to this diff. */
203221
isFinal: boolean;
204-
205-
/** Added data (e.g. line numbers) to show in the UI */
206-
added: number;
207-
/** Removed data (e.g. line numbers) to show in the UI */
208-
removed: number;
209222
}
210223

211224
export const enum ModifiedFileEntryState {

0 commit comments

Comments
 (0)