@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
88import { IModeService } from 'vs/editor/common/services/modeService' ;
99import { IModelService } from 'vs/editor/common/services/modelService' ;
1010import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel' ;
11- import { WorkspaceEdit , TextEdit , WorkspaceTextEdit , WorkspaceFileEdit , WorkspaceEditMetadata } from 'vs/editor/common/modes' ;
11+ import { WorkspaceEdit , WorkspaceTextEdit , WorkspaceFileEdit , WorkspaceEditMetadata } from 'vs/editor/common/modes' ;
1212import { DisposableStore } from 'vs/base/common/lifecycle' ;
1313import { mergeSort , coalesceInPlace } from 'vs/base/common/arrays' ;
1414import { Range } from 'vs/editor/common/core/range' ;
@@ -21,34 +21,35 @@ import { ConflictDetector } from 'vs/workbench/services/bulkEdit/browser/conflic
2121import { values } from 'vs/base/common/map' ;
2222import { localize } from 'vs/nls' ;
2323
24- export class CheckedObject {
24+ export class CheckedStates < T extends object > {
2525
26- private _checked : boolean = true ;
26+ private readonly _states = new WeakMap < T , boolean > ( ) ;
2727
28- constructor ( protected _emitter : Emitter < any > ) { }
28+ private readonly _onDidChange = new Emitter < T > ( ) ;
29+ readonly onDidChange : Event < T > = this . _onDidChange . event ;
2930
30- updateChecked ( checked : boolean ) {
31- if ( this . _checked !== checked ) {
32- this . _checked = checked ;
33- this . _emitter . fire ( this ) ;
34- }
31+ dispose ( ) : void {
32+ this . _onDidChange . dispose ( ) ;
33+ }
34+
35+ isChecked ( obj : T ) : boolean {
36+ return this . _states . get ( obj ) ?? false ;
3537 }
3638
37- isChecked ( ) : boolean {
38- return this . _checked ;
39+ updateChecked ( obj : T , value : boolean ) : void {
40+ if ( this . _states . get ( obj ) !== value ) {
41+ this . _states . set ( obj , value ) ;
42+ this . _onDidChange . fire ( obj ) ;
43+ }
3944 }
4045}
4146
42- export class BulkTextEdit extends CheckedObject {
47+ export class BulkTextEdit {
4348
4449 constructor (
4550 readonly parent : BulkFileOperation ,
46- readonly textEdit : WorkspaceTextEdit ,
47- emitter : Emitter < BulkFileOperation | BulkTextEdit >
48- ) {
49- super ( emitter ) ;
50- this . updateChecked ( ! textEdit . metadata ?. needsConfirmation ) ;
51- }
51+ readonly textEdit : WorkspaceTextEdit
52+ ) { }
5253}
5354
5455export const enum BulkFileOperationType {
@@ -58,7 +59,7 @@ export const enum BulkFileOperationType {
5859 Rename = 8 ,
5960}
6061
61- export class BulkFileOperation extends CheckedObject {
62+ export class BulkFileOperation {
6263
6364 type : BulkFileOperationType = 0 ;
6465 textEdits : BulkTextEdit [ ] = [ ] ;
@@ -68,24 +69,16 @@ export class BulkFileOperation extends CheckedObject {
6869 constructor (
6970 readonly uri : URI ,
7071 readonly parent : BulkFileOperations
71- ) {
72- super ( parent . _onDidChangeCheckedState ) ;
73- }
72+ ) { }
7473
7574 addEdit ( index : number , type : BulkFileOperationType , edit : WorkspaceTextEdit | WorkspaceFileEdit , ) {
7675 this . type |= type ;
7776 this . originalEdits . set ( index , edit ) ;
7877 if ( WorkspaceTextEdit . is ( edit ) ) {
79- this . textEdits . push ( new BulkTextEdit ( this , edit , this . _emitter ) ) ;
78+ this . textEdits . push ( new BulkTextEdit ( this , edit ) ) ;
8079
81- } else {
82- if ( type === BulkFileOperationType . Rename ) {
83- this . newUri = edit . newUri ;
84- }
85- // one needsConfirmation is enough to uncheck this item
86- if ( this . isChecked ( ) && edit . metadata ?. needsConfirmation ) {
87- this . updateChecked ( false ) ;
88- }
80+ } else if ( type === BulkFileOperationType . Rename ) {
81+ this . newUri = edit . newUri ;
8982 }
9083 }
9184}
@@ -118,8 +111,7 @@ export class BulkFileOperations {
118111 return await result . _init ( ) ;
119112 }
120113
121- readonly _onDidChangeCheckedState = new Emitter < BulkFileOperation | BulkTextEdit > ( ) ;
122- readonly onDidChangeCheckedState : Event < BulkFileOperation | BulkTextEdit > = this . _onDidChangeCheckedState . event ;
114+ readonly checked = new CheckedStates < WorkspaceFileEdit | WorkspaceTextEdit > ( ) ;
123115
124116 readonly fileOperations : BulkFileOperation [ ] = [ ] ;
125117 readonly categories : BulkCategory [ ] = [ ] ;
@@ -131,23 +123,10 @@ export class BulkFileOperations {
131123 @IInstantiationService instaService : IInstantiationService ,
132124 ) {
133125 this . conflicts = instaService . createInstance ( ConflictDetector , _bulkEdit ) ;
134-
135- // reflect checked-state for files in all categories the file occurs in
136- this . _onDidChangeCheckedState . event ( e => {
137- if ( e instanceof BulkFileOperation && e . parent ) {
138- for ( let item of this . categories ) {
139- for ( let file of item . fileOperations ) {
140- if ( file . uri . toString ( ) === e . uri . toString ( ) ) {
141- file . updateChecked ( e . isChecked ( ) ) ;
142- }
143- }
144- }
145- }
146- } ) ;
147126 }
148127
149128 dispose ( ) : void {
150- this . _onDidChangeCheckedState . dispose ( ) ;
129+ this . checked . dispose ( ) ;
151130 this . conflicts . dispose ( ) ;
152131 }
153132
@@ -163,6 +142,9 @@ export class BulkFileOperations {
163142 let uri : URI ;
164143 let type : BulkFileOperationType ;
165144
145+ // store inital checked state
146+ this . checked . updateChecked ( edit , ! edit . metadata ?. needsConfirmation ) ;
147+
166148 if ( WorkspaceTextEdit . is ( edit ) ) {
167149 type = BulkFileOperationType . TextEdit ;
168150 uri = edit . resource ;
@@ -234,43 +216,79 @@ export class BulkFileOperations {
234216 return this ;
235217 }
236218
237- asWorkspaceEdit ( ) : WorkspaceEdit {
219+ getWorkspaceEdit ( ) : WorkspaceEdit {
238220 const result : WorkspaceEdit = { edits : [ ] } ;
239221 let allAccepted = true ;
240- for ( let file of this . fileOperations ) {
241222
242- if ( ! file . isChecked ( ) ) {
243- allAccepted = false ;
223+ for ( let i = 0 ; i < this . _bulkEdit . edits . length ; i ++ ) {
224+ const edit = this . _bulkEdit . edits [ i ] ;
225+ if ( this . checked . isChecked ( edit ) ) {
226+ result . edits [ i ] = edit ;
244227 continue ;
245228 }
229+ allAccepted = false ;
230+ }
246231
247- const keyOfEdit = ( edit : TextEdit ) => JSON . stringify ( edit ) ;
248- const checkedEdits = new Set < string > ( ) ;
232+ if ( allAccepted ) {
233+ return this . _bulkEdit ;
234+ }
249235
250- for ( let edit of file . textEdits ) {
251- if ( edit . isChecked ( ) ) {
252- checkedEdits . add ( keyOfEdit ( edit . textEdit . edit ) ) ;
253- }
254- }
236+ // not all edits have been accepted
237+ coalesceInPlace ( result . edits ) ;
238+ return result ;
239+ }
255240
256- file . originalEdits . forEach ( ( value , idx ) => {
241+ getFileEdits ( uri : URI ) : IIdentifiedSingleEditOperation [ ] {
257242
258- if ( WorkspaceTextEdit . is ( value ) && ! checkedEdits . has ( keyOfEdit ( value . edit ) ) ) {
259- allAccepted = false ;
260- return ;
261- }
243+ for ( let file of this . fileOperations ) {
244+ if ( file . uri . toString ( ) === uri . toString ( ) ) {
262245
263- result . edits [ idx ] = value ;
246+ const result : IIdentifiedSingleEditOperation [ ] = [ ] ;
247+ let ignoreAll = false ;
264248
265- } ) ;
249+ file . originalEdits . forEach ( edit => {
250+
251+ if ( WorkspaceTextEdit . is ( edit ) ) {
252+ if ( this . checked . isChecked ( edit ) ) {
253+ result . push ( EditOperation . replaceMove ( Range . lift ( edit . edit . range ) , edit . edit . text ) ) ;
254+ }
255+
256+ } else if ( ! this . checked . isChecked ( edit ) ) {
257+ // UNCHECKED WorkspaceFileEdit disables all text edits
258+ ignoreAll = true ;
259+ }
260+ } ) ;
261+
262+ if ( ignoreAll ) {
263+ return [ ] ;
264+ }
265+
266+ return mergeSort (
267+ result ,
268+ ( a , b ) => Range . compareRangesUsingStarts ( a . range , b . range )
269+ ) ;
270+ }
266271 }
267- if ( ! allAccepted ) {
268- // only return a new edit when something has changed
269- coalesceInPlace ( result . edits ) ;
270- return result ;
272+ return [ ] ;
273+ }
274+
275+ getUriOfEdit ( edit : WorkspaceFileEdit | WorkspaceTextEdit ) : URI {
276+ if ( WorkspaceTextEdit . is ( edit ) ) {
277+ return edit . resource ;
271278 }
272- return this . _bulkEdit ;
273279
280+ for ( let file of this . fileOperations ) {
281+ let found = false ;
282+ file . originalEdits . forEach ( value => {
283+ if ( ! found && value === edit ) {
284+ found = true ;
285+ }
286+ } ) ;
287+ if ( found ) {
288+ return file . uri ;
289+ }
290+ }
291+ throw new Error ( 'invalid edit' ) ;
274292 }
275293}
276294
@@ -308,30 +326,26 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider {
308326
309327 private async _init ( ) {
310328 for ( let operation of this . _operations . fileOperations ) {
311- await this . _applyTextEditsToPreviewModel ( operation ) ;
329+ await this . _applyTextEditsToPreviewModel ( operation . uri ) ;
312330 }
313- this . _disposables . add ( this . _operations . onDidChangeCheckedState ( element => {
314- let operation = element instanceof BulkFileOperation ? element : element . parent ;
315- this . _applyTextEditsToPreviewModel ( operation ) ;
331+ this . _disposables . add ( this . _operations . checked . onDidChange ( e => {
332+ const uri = this . _operations . getUriOfEdit ( e ) ;
333+ this . _applyTextEditsToPreviewModel ( uri ) ;
316334 } ) ) ;
317335 }
318336
319- private async _applyTextEditsToPreviewModel ( operation : BulkFileOperation ) {
320- const model = await this . _getOrCreatePreviewModel ( operation . uri ) ;
337+ private async _applyTextEditsToPreviewModel ( uri : URI ) {
338+ const model = await this . _getOrCreatePreviewModel ( uri ) ;
321339
322340 // undo edits that have been done before
323341 let undoEdits = this . _modelPreviewEdits . get ( model . id ) ;
324342 if ( undoEdits ) {
325343 model . applyEdits ( undoEdits ) ;
326344 }
327- // compute new edits
328- const newEdits = mergeSort (
329- operation . textEdits . filter ( edit => edit . isChecked ( ) && edit . parent . isChecked ( ) ) . map ( edit => EditOperation . replaceMove ( Range . lift ( edit . textEdit . edit . range ) , edit . textEdit . edit . text ) ) ,
330- ( a , b ) => Range . compareRangesUsingStarts ( a . range , b . range )
331- ) ;
332- // apply edits and keep undo edits
333- undoEdits = model . applyEdits ( newEdits ) ;
334- this . _modelPreviewEdits . set ( model . id , undoEdits ) ;
345+ // apply new edits and keep (future) undo edits
346+ const newEdits = this . _operations . getFileEdits ( uri ) ;
347+ const newUndoEdits = model . applyEdits ( newEdits ) ;
348+ this . _modelPreviewEdits . set ( model . id , newUndoEdits ) ;
335349 }
336350
337351 private async _getOrCreatePreviewModel ( uri : URI ) {
0 commit comments