6
6
7
7
8
8
import { equals as arraysEqual , binarySearch2 } from '../../../../../base/common/arrays.js' ;
9
+ import { equals as objectsEqual } from '../../../../../base/common/objects.js' ;
9
10
import { findLast } from '../../../../../base/common/arraysFind.js' ;
11
+ import { Iterable } from '../../../../../base/common/iterator.js' ;
10
12
import { DisposableStore } from '../../../../../base/common/lifecycle.js' ;
11
13
import { ResourceMap } from '../../../../../base/common/map.js' ;
12
14
import { derived , derivedOpts , IObservable , ITransaction , ObservablePromise , observableValue , transaction } from '../../../../../base/common/observable.js' ;
@@ -46,6 +48,22 @@ export class ChatEditingTimeline {
46
48
public readonly canUndo : IObservable < boolean > ;
47
49
public readonly canRedo : IObservable < boolean > ;
48
50
51
+ public readonly requestDisablement = derivedOpts < IChatRequestDisablement [ ] > ( { equalsFn : ( a , b ) => arraysEqual ( a , b , objectsEqual ) } , reader => {
52
+ const history = this . _linearHistory . read ( reader ) ;
53
+ const index = this . _linearHistoryIndex . read ( reader ) ;
54
+ const undoRequests : IChatRequestDisablement [ ] = [ ] ;
55
+ for ( const entry of history ) {
56
+ if ( ! entry . requestId ) {
57
+ // ignored
58
+ } else if ( entry . startIndex >= index ) {
59
+ undoRequests . push ( { requestId : entry . requestId } ) ;
60
+ } else if ( entry . startIndex + entry . stops . length > index ) {
61
+ undoRequests . push ( { requestId : entry . requestId , afterUndoStop : entry . stops [ ( index - 1 ) - entry . startIndex ] . stopId } ) ;
62
+ }
63
+ }
64
+ return undoRequests ;
65
+ } ) ;
66
+
49
67
constructor (
50
68
@IEditorWorkerService private readonly _editorWorkerService : IEditorWorkerService ,
51
69
@IInstantiationService private readonly _instantiationService : IInstantiationService ,
@@ -76,7 +94,7 @@ export class ChatEditingTimeline {
76
94
* Get the snapshot and history index for restoring, given requestId and stopId.
77
95
* If requestId is undefined, returns undefined (pending snapshot is managed by session).
78
96
*/
79
- public getSnapshotForRestore ( requestId : string | undefined , stopId : string | undefined ) : { stop : IChatEditingSessionStop ; historyIndex : number ; apply ( ) : void } | undefined {
97
+ public getSnapshotForRestore ( requestId : string | undefined , stopId : string | undefined ) : { stop : IChatEditingSessionStop ; apply ( ) : void } | undefined {
80
98
if ( requestId === undefined ) {
81
99
return undefined ;
82
100
}
@@ -85,7 +103,13 @@ export class ChatEditingTimeline {
85
103
return undefined ;
86
104
}
87
105
88
- return { stop : stopRef . stop , historyIndex : stopRef . historyIndex , apply : ( ) => this . _linearHistoryIndex . set ( stopRef . historyIndex + 1 , undefined ) } ;
106
+ // When rolling back to the first snapshot taken for a request, mark the
107
+ // entire request as undone.
108
+ const toIndex = stopRef . stop . stopId === undefined ? stopRef . historyIndex : stopRef . historyIndex + 1 ;
109
+ return {
110
+ stop : stopRef . stop ,
111
+ apply : ( ) => this . _linearHistoryIndex . set ( toIndex , undefined )
112
+ } ;
89
113
}
90
114
91
115
/**
@@ -119,22 +143,21 @@ export class ChatEditingTimeline {
119
143
return ;
120
144
}
121
145
122
- const snap = history [ snapIndex ] ;
146
+ const snap = { ... history [ snapIndex ] } ;
123
147
let stopIndex = snap . stops . findIndex ( ( s ) => s . stopId === undoStop ) ;
124
148
if ( stopIndex === - 1 ) {
125
149
return ;
126
150
}
127
151
152
+ let linearHistoryIndexIncr = 0 ;
128
153
if ( next ) {
129
154
if ( stopIndex === snap . stops . length - 1 ) {
130
- const postEdit = new ResourceMap ( snap . postEdit || ChatEditingTimeline . createEmptySnapshot ( undefined ) . entries ) ;
131
- if ( ! snap . postEdit || ! entry . equalsSnapshot ( postEdit . get ( entry . modifiedURI ) as ISnapshotEntry | undefined ) ) {
132
- postEdit . set ( entry . modifiedURI , entry . createSnapshot ( requestId , ChatEditingTimeline . POST_EDIT_STOP_ID ) ) ;
133
- const newHistory = history . slice ( ) ;
134
- newHistory [ snapIndex ] = { ...snap , postEdit } ;
135
- this . _linearHistory . set ( newHistory , tx ) ;
155
+ if ( snap . stops [ stopIndex ] . stopId === ChatEditingTimeline . POST_EDIT_STOP_ID ) {
156
+ throw new Error ( 'cannot duplicate post-edit stop' ) ;
136
157
}
137
- return ;
158
+
159
+ snap . stops = snap . stops . concat ( ChatEditingTimeline . createEmptySnapshot ( ChatEditingTimeline . POST_EDIT_STOP_ID ) ) ;
160
+ linearHistoryIndexIncr ++ ;
138
161
}
139
162
stopIndex ++ ;
140
163
}
@@ -149,10 +172,15 @@ export class ChatEditingTimeline {
149
172
150
173
const newStop = snap . stops . slice ( ) ;
151
174
newStop [ stopIndex ] = { ...stop , entries : newMap } ;
175
+ snap . stops = newStop ;
152
176
153
177
const newHistory = history . slice ( ) ;
154
- newHistory [ snapIndex ] = { ...snap , stops : newStop } ;
178
+ newHistory [ snapIndex ] = snap ;
179
+
155
180
this . _linearHistory . set ( newHistory , tx ) ;
181
+ if ( linearHistoryIndexIncr ) {
182
+ this . _linearHistoryIndex . set ( this . _linearHistoryIndex . get ( ) + linearHistoryIndexIncr , tx ) ;
183
+ }
156
184
}
157
185
158
186
/**
@@ -161,23 +189,40 @@ export class ChatEditingTimeline {
161
189
* pushed into the history.
162
190
*/
163
191
public getUndoSnapshot ( ) : { stop : IChatEditingSessionStop ; apply ( ) : void } | undefined {
164
- const idx = this . _linearHistoryIndex . get ( ) - 2 ;
165
- const entry = this . getHistoryEntryByLinearIndex ( idx ) ;
166
- if ( entry ) {
167
- return { stop : entry . stop , apply : ( ) => this . _linearHistoryIndex . set ( idx + 1 , undefined ) } ;
168
- }
169
- return undefined ;
192
+ return this . getUndoRedoSnapshot ( - 1 ) ;
170
193
}
171
194
172
195
/**
173
196
* Get the redo snapshot (next in history), or undefined if at end.
174
197
*/
175
198
public getRedoSnapshot ( ) : { stop : IChatEditingSessionStop ; apply ( ) : void } | undefined {
176
- const idx = this . _linearHistoryIndex . get ( ) ;
177
- const entry = this . getHistoryEntryByLinearIndex ( idx ) ;
199
+ return this . getUndoRedoSnapshot ( 1 ) ;
200
+ }
201
+
202
+ private getUndoRedoSnapshot ( direction : number ) {
203
+ let idx = this . _linearHistoryIndex . get ( ) - 1 ;
204
+ const max = getMaxHistoryIndex ( this . _linearHistory . get ( ) ) ;
205
+ const startEntry = this . getHistoryEntryByLinearIndex ( idx ) ;
206
+ let entry = startEntry ;
207
+ if ( ! startEntry ) {
208
+ return undefined ;
209
+ }
210
+
211
+ do {
212
+ idx += direction ;
213
+ entry = this . getHistoryEntryByLinearIndex ( idx ) ;
214
+ } while (
215
+ idx + direction < max &&
216
+ idx + direction >= 0 &&
217
+ entry &&
218
+ ! ( direction === - 1 && entry . entry . requestId !== startEntry . entry . requestId ) &&
219
+ ! stopProvidesNewData ( startEntry . stop , entry . stop )
220
+ ) ;
221
+
178
222
if ( entry ) {
179
223
return { stop : entry . stop , apply : ( ) => this . _linearHistoryIndex . set ( idx + 1 , undefined ) } ;
180
224
}
225
+
181
226
return undefined ;
182
227
}
183
228
@@ -221,23 +266,27 @@ export class ChatEditingTimeline {
221
266
if ( entry . startIndex >= linearHistoryPtr ) {
222
267
break ;
223
268
} else if ( linearHistoryPtr - entry . startIndex < entry . stops . length ) {
224
- newLinearHistory . push ( { requestId : entry . requestId , stops : entry . stops . slice ( 0 , linearHistoryPtr - entry . startIndex ) , startIndex : entry . startIndex , postEdit : undefined } ) ;
269
+ newLinearHistory . push ( { requestId : entry . requestId , stops : entry . stops . slice ( 0 , linearHistoryPtr - entry . startIndex ) , startIndex : entry . startIndex } ) ;
225
270
} else {
226
271
newLinearHistory . push ( entry ) ;
227
272
}
228
273
}
229
274
230
275
const lastEntry = newLinearHistory . at ( - 1 ) ;
231
276
if ( requestId && lastEntry ?. requestId === requestId ) {
232
- if ( lastEntry . postEdit && undoStop ) {
277
+ const hadPostEditStop = lastEntry . stops . at ( - 1 ) ?. stopId === ChatEditingTimeline . POST_EDIT_STOP_ID && undoStop ;
278
+ if ( hadPostEditStop ) {
233
279
const rebaseUri = ( uri : URI ) => uri . with ( { query : uri . query . replace ( ChatEditingTimeline . POST_EDIT_STOP_ID , undoStop ) } ) ;
234
- for ( const [ uri , prev ] of lastEntry . postEdit . entries ( ) ) {
280
+ for ( const [ uri , prev ] of lastEntry . stops . at ( - 1 ) ! . entries ) {
235
281
snapshot . entries . set ( uri , { ...prev , snapshotUri : rebaseUri ( prev . snapshotUri ) , resource : rebaseUri ( prev . resource ) } ) ;
236
282
}
237
283
}
238
- newLinearHistory [ newLinearHistory . length - 1 ] = { ...lastEntry , stops : [ ...lastEntry . stops , snapshot ] , postEdit : undefined } ;
284
+ newLinearHistory [ newLinearHistory . length - 1 ] = {
285
+ ...lastEntry ,
286
+ stops : [ ...hadPostEditStop ? lastEntry . stops . slice ( 0 , - 1 ) : lastEntry . stops , snapshot ]
287
+ } ;
239
288
} else {
240
- newLinearHistory . push ( { requestId, startIndex : lastEntry ? lastEntry . startIndex + lastEntry . stops . length : 0 , stops : [ snapshot ] , postEdit : undefined } ) ;
289
+ newLinearHistory . push ( { requestId, startIndex : lastEntry ? lastEntry . startIndex + lastEntry . stops . length : 0 , stops : [ snapshot ] } ) ;
241
290
}
242
291
243
292
transaction ( ( tx ) => {
@@ -247,25 +296,6 @@ export class ChatEditingTimeline {
247
296
} ) ;
248
297
}
249
298
250
- /**
251
- * Gets chat disablement entries for the current timeline state.
252
- */
253
- public getRequestDisablement ( ) {
254
- const history = this . _linearHistory . get ( ) ;
255
- const index = this . _linearHistoryIndex . get ( ) ;
256
- const undoRequests : IChatRequestDisablement [ ] = [ ] ;
257
- for ( const entry of history ) {
258
- if ( ! entry . requestId ) {
259
- // ignored
260
- } else if ( entry . startIndex >= index ) {
261
- undoRequests . push ( { requestId : entry . requestId } ) ;
262
- } else if ( entry . startIndex + entry . stops . length > index ) {
263
- undoRequests . push ( { requestId : entry . requestId , afterUndoStop : entry . stops [ index - entry . startIndex ] . stopId } ) ;
264
- }
265
- }
266
- return undoRequests ;
267
- }
268
-
269
299
/**
270
300
* Gets diff for text entries between stops.
271
301
* @param entriesContent Observable that observes either snapshot entry
@@ -390,6 +420,10 @@ export class ChatEditingTimeline {
390
420
}
391
421
}
392
422
423
+ function stopProvidesNewData ( origin : IChatEditingSessionStop , target : IChatEditingSessionStop ) {
424
+ return Iterable . some ( target . entries , ( [ uri , e ] ) => origin . entries . get ( uri ) ?. current !== e . current ) ;
425
+ }
426
+
393
427
function getMaxHistoryIndex ( history : readonly IChatEditingSessionSnapshot [ ] ) {
394
428
const lastHistory = history . at ( - 1 ) ;
395
429
return lastHistory ? lastHistory . startIndex + lastHistory . stops . length : 0 ;
@@ -415,14 +449,11 @@ function getCurrentAndNextStop(requestId: string, stopId: string | undefined, hi
415
449
const nextStop = stopIndex < snapshot . stops . length - 1
416
450
? snapshot . stops [ stopIndex + 1 ]
417
451
: undefined ;
418
- const next = nextStop ?. entries || snapshot . postEdit ;
419
-
420
-
421
- if ( ! next ) {
452
+ if ( ! nextStop ) {
422
453
return undefined ;
423
454
}
424
455
425
- return { current, currentStopId : currentStop . stopId , next, nextStopId : nextStop ? .stopId || ChatEditingTimeline . POST_EDIT_STOP_ID } ;
456
+ return { current, currentStopId : currentStop . stopId , next : nextStop . entries , nextStopId : nextStop . stopId } ;
426
457
}
427
458
428
459
function getFirstAndLastStop ( uri : URI , history : readonly IChatEditingSessionSnapshot [ ] ) {
@@ -439,12 +470,6 @@ function getFirstAndLastStop(uri: URI, history: readonly IChatEditingSessionSnap
439
470
let lastStopWithUriId : string | undefined ;
440
471
for ( let i = history . length - 1 ; i >= 0 ; i -- ) {
441
472
const snapshot = history [ i ] ;
442
- if ( snapshot . postEdit ?. has ( uri ) ) {
443
- lastStopWithUri = snapshot . postEdit ;
444
- lastStopWithUriId = ChatEditingTimeline . POST_EDIT_STOP_ID ;
445
- break ;
446
- }
447
-
448
473
const stop = findLast ( snapshot . stops , s => s . entries . has ( uri ) ) ;
449
474
if ( stop ) {
450
475
lastStopWithUri = stop . entries ;
0 commit comments