@@ -71,6 +71,10 @@ class ChangeBuilderImpl implements ChangeBuilder {
7171 /// by a given correction producer.
7272 int modificationCount = 0 ;
7373
74+ /// The data used to revert any changes made since the last time [commit] was
75+ /// called.
76+ final _ChangeBuilderRevertData _revertData = _ChangeBuilderRevertData ();
77+
7478 /// Initialize a newly created change builder. If the builder will be used to
7579 /// create changes for Dart files, then either a [session] or a [workspace]
7680 /// must be provided (but not both).
@@ -154,6 +158,7 @@ class ChangeBuilderImpl implements ChangeBuilder {
154158 "Can't add multiple edits concurrently for the same file" );
155159 }
156160 _dartFileEditBuilders[path] = builder;
161+ _revertData._addedDartFileEditBuilders.add (path);
157162 }
158163 }
159164 if (builder != null ) {
@@ -190,6 +195,7 @@ class ChangeBuilderImpl implements ChangeBuilder {
190195 if (builder == null ) {
191196 builder = FileEditBuilderImpl (this , path, 0 );
192197 _genericFileEditBuilders[path] = builder;
198+ _revertData._addedGenericFileEditBuilders.add (path);
193199 }
194200 builder.currentChangeDescription = currentChangeDescription;
195201 buildFileEdit (builder);
@@ -217,11 +223,43 @@ class ChangeBuilderImpl implements ChangeBuilder {
217223 recover: true ),
218224 0 );
219225 _yamlFileEditBuilders[path] = builder;
226+ _revertData._addedYamlFileEditBuilders.add (path);
220227 }
221228 builder.currentChangeDescription = currentChangeDescription;
222229 buildFileEdit (builder);
223230 }
224231
232+ /// Commit the changes that have been made up to this point.
233+ void commit () {
234+ // Capture the current values of simple values.
235+ _revertData._selection = _selection;
236+ _revertData._selectionRange = _selectionRange;
237+ _revertData._currentChangeDescription = currentChangeDescription;
238+ _revertData._modificationCount = modificationCount;
239+
240+ // Discard any information about changes to maps.
241+ _revertData._addedLinkedEditGroups.clear ();
242+ _revertData._addedLinkedEditGroupPositions.clear ();
243+ _revertData._addedLinkedEditGroupSuggestions.clear ();
244+
245+ _revertData._addedGenericFileEditBuilders.clear ();
246+ _revertData._addedDartFileEditBuilders.clear ();
247+ _revertData._addedYamlFileEditBuilders.clear ();
248+
249+ // Commit the changes in any pre-existing builders.
250+ for (var builder in _genericFileEditBuilders.values) {
251+ builder.commit ();
252+ }
253+ for (var builder in _dartFileEditBuilders.values) {
254+ builder.commit ();
255+ }
256+ for (var builder in _yamlFileEditBuilders.values) {
257+ builder.commit ();
258+ }
259+ }
260+
261+ @Deprecated ('Copying change builders is expensive. Internal users of this '
262+ 'method now use `commit` and `revert` instead.' )
225263 @override
226264 ChangeBuilder copy () {
227265 var copy = ChangeBuilderImpl (workspace: workspace, eol: eol);
@@ -273,6 +311,7 @@ class ChangeBuilderImpl implements ChangeBuilder {
273311 if (group == null ) {
274312 group = LinkedEditGroup .empty ();
275313 _linkedEditGroups[groupName] = group;
314+ _revertData._addedLinkedEditGroups[groupName] = group;
276315 }
277316 return group;
278317 }
@@ -284,6 +323,65 @@ class ChangeBuilderImpl implements ChangeBuilder {
284323 _yamlFileEditBuilders.containsKey (path);
285324 }
286325
326+ /// Revert any changes made since the last time [commit] was called.
327+ void revert () {
328+ // Set simple values back to their previous values.
329+ _selection = _revertData._selection;
330+ _selectionRange = _revertData._selectionRange;
331+ currentChangeDescription = _revertData._currentChangeDescription;
332+ modificationCount = _revertData._modificationCount;
333+
334+ // Remove any linked edit groups that have been added.
335+ for (var entry in _revertData._addedLinkedEditGroups.entries) {
336+ _linkedEditGroups.remove (entry.key);
337+ }
338+ // Remove any positions or suggestions that were added to pre-existing
339+ // link edit groups.
340+ //
341+ // Note that this code assumes that the lengths of the groups have not been
342+ // changed. It's invalid to change the length, but there's no code to
343+ // validate that it hasn't been changed.
344+ for (var entry in _revertData._addedLinkedEditGroupPositions.entries) {
345+ for (var position in entry.value) {
346+ entry.key.positions.remove (position);
347+ }
348+ }
349+ for (var entry in _revertData._addedLinkedEditGroupSuggestions.entries) {
350+ for (var suggestion in entry.value) {
351+ entry.key.suggestions.remove (suggestion);
352+ }
353+ }
354+ // Discard any data about linked edit groups because it's no longer needed.
355+ _revertData._addedLinkedEditGroups.clear ();
356+ _revertData._addedLinkedEditGroupPositions.clear ();
357+ _revertData._addedLinkedEditGroupSuggestions.clear ();
358+
359+ // Remove any file edit builders that have been added.
360+ for (var path in _revertData._addedGenericFileEditBuilders) {
361+ _genericFileEditBuilders.remove (path);
362+ }
363+ for (var path in _revertData._addedDartFileEditBuilders) {
364+ _dartFileEditBuilders.remove (path);
365+ }
366+ for (var path in _revertData._addedYamlFileEditBuilders) {
367+ _yamlFileEditBuilders.remove (path);
368+ }
369+ // Revert the data changes in any pre-existing builders.
370+ for (var builder in _genericFileEditBuilders.values) {
371+ builder.revert ();
372+ }
373+ for (var builder in _dartFileEditBuilders.values) {
374+ builder.revert ();
375+ }
376+ for (var builder in _yamlFileEditBuilders.values) {
377+ builder.revert ();
378+ }
379+ // Discard any data about file builders because it's no longer needed.
380+ _revertData._addedGenericFileEditBuilders.clear ();
381+ _revertData._addedDartFileEditBuilders.clear ();
382+ _revertData._addedYamlFileEditBuilders.clear ();
383+ }
384+
287385 @override
288386 void setSelection (Position position) {
289387 _selection = position;
@@ -435,8 +533,15 @@ class EditBuilderImpl implements EditBuilder {
435533 fileEditBuilder.changeBuilder._lockedPositions.add (position);
436534 var group = fileEditBuilder.changeBuilder.getLinkedEditGroup (groupName);
437535 group.addPosition (position, length);
536+ var revertData = fileEditBuilder.changeBuilder._revertData;
537+ revertData._addedLinkedEditGroupPositions
538+ .putIfAbsent (group, () => [])
539+ .add (position);
438540 for (var suggestion in builder.suggestions) {
439541 group.addSuggestion (suggestion);
542+ revertData._addedLinkedEditGroupSuggestions
543+ .putIfAbsent (group, () => [])
544+ .add (suggestion);
440545 }
441546 }
442547 }
@@ -508,6 +613,8 @@ class FileEditBuilderImpl implements FileEditBuilder {
508613 /// single description.
509614 String ? currentChangeDescription;
510615
616+ final _FileEditBuilderRevertData _revertData = _FileEditBuilderRevertData ();
617+
511618 /// Initialize a newly created builder to build a source file edit within the
512619 /// change being built by the given [changeBuilder] . The file being edited has
513620 /// the given absolute [path] and [timeStamp] .
@@ -542,6 +649,10 @@ class FileEditBuilderImpl implements FileEditBuilder {
542649 var position =
543650 Position (fileEdit.file, range.offset + _deltaToOffset (range.offset));
544651 group.addPosition (position, range.length);
652+ var revertData = changeBuilder._revertData;
653+ revertData._addedLinkedEditGroupPositions
654+ .putIfAbsent (group, () => [])
655+ .add (position);
545656 }
546657
547658 @override
@@ -575,6 +686,15 @@ class FileEditBuilderImpl implements FileEditBuilder {
575686 }
576687 }
577688
689+ /// Commit the changes that have been made up to this point.
690+ void commit () {
691+ _revertData._currentChangeDescription = currentChangeDescription;
692+
693+ _revertData._addedEdits.clear ();
694+ }
695+
696+ @Deprecated ('Copying change builders is expensive. Internal users of this '
697+ 'method now use `commit` and `revert` instead.' )
578698 FileEditBuilderImpl copyWith (ChangeBuilderImpl changeBuilder) {
579699 var copy =
580700 FileEditBuilderImpl (changeBuilder, fileEdit.file, fileEdit.fileStamp);
@@ -594,8 +714,16 @@ class FileEditBuilderImpl implements FileEditBuilder {
594714 }
595715
596716 /// Replace edits in the [range] with the given [edit] .
717+ ///
597718 /// The [range] is relative to the original code.
598719 void replaceEdits (SourceRange range, SourceEdit edit) {
720+ // This does not record the edits that are being replaced, so we cannot
721+ // correctly revert the current transaction.
722+ //
723+ // I think this is ok because this method is only called from
724+ // `DartFileEditBuilder.format`, which isn't called from the bulk fix
725+ // processor. But we will need to fix this if we start using it in a context
726+ // where we do want to be able to revert such changes.
599727 fileEdit.edits.removeWhere ((edit) {
600728 if (range.contains (edit.offset)) {
601729 if (! range.contains (edit.end)) {
@@ -611,10 +739,18 @@ class FileEditBuilderImpl implements FileEditBuilder {
611739 _addEdit (edit);
612740 }
613741
742+ /// Revert any changes made since the last time [commit] was called.
743+ void revert () {
744+ currentChangeDescription = _revertData._currentChangeDescription;
745+
746+ fileEdit.edits.removeWhere (_revertData._addedEdits.contains);
747+ }
748+
614749 /// Add the edit from the given [edit] to the edits associated with the
615750 /// current file.
616751 void _addEdit (SourceEdit edit, {bool insertBeforeExisting = false }) {
617752 fileEdit.add (edit, insertBeforeExisting: insertBeforeExisting);
753+ _revertData._addedEdits.add (edit);
618754 var delta = _editDelta (edit);
619755 changeBuilder._updatePositions (edit.offset, delta);
620756 changeBuilder._lockedPositions.clear ();
@@ -710,6 +846,75 @@ class LinkedEditBuilderImpl implements LinkedEditBuilder {
710846 }
711847}
712848
849+ /// The data used to revert any changes made to a [ChangeBuilder] since the last
850+ /// time [commit] was called.
851+ class _ChangeBuilderRevertData {
852+ /// The last committed value of the change builder's `_selection` .
853+ Position ? _selection;
854+
855+ /// The last committed value of the change builder's `_selectionRange` .
856+ SourceRange ? _selectionRange;
857+
858+ /// The last committed value of the change builder's
859+ /// `currentChangeDescription` .
860+ String ? _currentChangeDescription;
861+
862+ /// The last committed value of the change builder's `modificationCount` .
863+ int _modificationCount = 0 ;
864+
865+ /// A map from the names of linked edit groups to the linked edit groups that
866+ /// have been added since the last commit.
867+ final Map <String , LinkedEditGroup > _addedLinkedEditGroups = {};
868+
869+ /// A map from pre-existing linked edit groups to the positions that were
870+ /// added to the group.
871+ final Map <LinkedEditGroup , List <Position >> _addedLinkedEditGroupPositions =
872+ {};
873+
874+ /// A map from pre-existing linked edit groups to the suggestions that were
875+ /// added to the group.
876+ final Map <LinkedEditGroup , List <LinkedEditSuggestion >>
877+ _addedLinkedEditGroupSuggestions = {};
878+
879+ /// A map of absolute normalized path to generic file edit builders that have
880+ /// been added since the last commit.
881+ final Set <String > _addedGenericFileEditBuilders = {};
882+
883+ /// A map of absolute normalized path to Dart file edit builders that have
884+ /// been added since the last commit.
885+ final Set <String > _addedDartFileEditBuilders = {};
886+
887+ /// A map of absolute normalized path to YAML file edit builders that have
888+ /// been added since the last commit.
889+ final Set <String > _addedYamlFileEditBuilders = {};
890+ }
891+
892+ /// The data used to revert any changes made to a [FileEditBuilder] since the
893+ /// last time [commit] was called.
894+ class _FileEditBuilderRevertData {
895+ /// The last committed value of the change builder's
896+ /// `currentChangeDescription` .
897+ String ? _currentChangeDescription;
898+
899+ // This needs to be an identify set because it is possible for the data-driven
900+ // fixes support to produce the same set of (multiple) edits multiple times.
901+ // More investigation is required to determine whether that behavior is a bug
902+ // in the data-driven fixes support or whether this is (or can be) caused by
903+ // the data being used by it. If it is a bug that can be fixed, then we can
904+ // safely use a non-identiy set, though leaving it won't cause any problems.
905+ //
906+ // In either case, when that happens, the second set of edits will usually
907+ // conflict with the previous set of edits. Because the edits are the same,
908+ // they will return `true` from `==`, causing some of the edits to not be
909+ // recorded in this set. When the conflict is detected and an attempt is made
910+ // to revert the changes, only the one edit in this set will be removed from
911+ // the builder's list, making it impossible to correctly revert the change.
912+ //
913+ // The other option would be to make this a list, but doing so would decrease
914+ // the performance of `revert`.
915+ final Set <SourceEdit > _addedEdits = HashSet .identity ();
916+ }
917+
713918/// Workspace that wraps a single [AnalysisSession] .
714919class _SingleSessionWorkspace extends ChangeWorkspace {
715920 final AnalysisSession session;
0 commit comments