redocommand.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module undo/redocommand
  7. */
  8. import BaseCommand from './basecommand';
  9. import { transformDeltaSets } from '@ckeditor/ckeditor5-engine/src/model/delta/transform';
  10. /**
  11. * The redo command stores {@link module:engine/model/batch~Batch batches} that were used to undo a batch by
  12. * {@link module:undo/undocommand~UndoCommand}. It is able to redo a previously undone batch by reversing the undoing
  13. * batches created by `UndoCommand`. The reversed batch is also transformed by batches from
  14. * {@link module:engine/model/document~Document#history history} that happened after it and are not other redo batches.
  15. *
  16. * The redo command also takes care of restoring the {@link module:engine/model/document~Document#selection document selection}
  17. * to the state before an undone batch was applied.
  18. *
  19. * @extends module:undo/basecommand~BaseCommand
  20. */
  21. export default class RedoCommand extends BaseCommand {
  22. /**
  23. * Executes the command. This method reverts the last {@link module:engine/model/batch~Batch batch} added to
  24. * the command's stack, applies the reverted and transformed version on the
  25. * {@link module:engine/model/document~Document document} and removes the batch from the stack.
  26. * Then, it restores the {@link module:engine/model/document~Document#selection document selection}.
  27. *
  28. * @protected
  29. */
  30. _doExecute() {
  31. const item = this._stack.pop();
  32. // All changes have to be done in one `enqueueChanges` callback so other listeners will not
  33. // step between consecutive deltas, or won't do changes to the document before selection is properly restored.
  34. this.editor.document.enqueueChanges( () => {
  35. const lastDelta = item.batch.deltas[ item.batch.deltas.length - 1 ];
  36. const nextBaseVersion = lastDelta.baseVersion + lastDelta.operations.length;
  37. // Selection state is from the moment after undo happened. It needs to be transformed by all the deltas
  38. // that happened after the selection state got saved. Unfortunately it is tricky, because those deltas
  39. // are already compressed in the history (they are removed).
  40. // Because of that we will transform the selection only by non-redo deltas
  41. const deltas = Array.from( this.editor.document.history.getDeltas( nextBaseVersion ) ).filter( ( delta ) => {
  42. return !this._createdBatches.has( delta.batch );
  43. } );
  44. this._restoreSelection( item.selection.ranges, item.selection.isBackward, deltas );
  45. this._redo( item.batch );
  46. } );
  47. this.refreshState();
  48. }
  49. /**
  50. * Redoes a batch by reversing the batch that has undone it, transforming that batch and applying it. This is
  51. * a helper method for {@link #_doExecute}.
  52. *
  53. * @private
  54. * @param {module:engine/model/batch~Batch} storedBatch The batch whose deltas will be reversed, transformed and applied.
  55. */
  56. _redo( storedBatch ) {
  57. const document = this.editor.document;
  58. // All changes done by the command execution will be saved as one batch.
  59. const redoingBatch = document.batch();
  60. this._createdBatches.add( redoingBatch );
  61. const deltasToRedo = storedBatch.deltas.slice();
  62. deltasToRedo.reverse();
  63. // We will process each delta from `storedBatch`, in reverse order. If there was deltas A, B and C in stored batch,
  64. // we need to revert them in reverse order, so first reverse C, then B, then A.
  65. for ( let deltaToRedo of deltasToRedo ) {
  66. // Keep in mind that all algorithms return arrays. That's because the transformation might result in multiple
  67. // deltas, so we need arrays to handle them anyway. To simplify algorithms, it is better to always have arrays
  68. // in mind. For simplicity reasons, we will use singular form in descriptions and names.
  69. const nextBaseVersion = deltaToRedo.baseVersion + deltaToRedo.operations.length;
  70. // As stated above, convert delta to array of deltas.
  71. let reversedDelta = [ deltaToRedo.getReversed() ];
  72. // 1. Transform that delta by deltas from history that happened after it.
  73. // Omit deltas from "redo" batches, because reversed delta already bases on them. Transforming by them
  74. // again will result in incorrect deltas.
  75. for ( let historyDelta of document.history.getDeltas( nextBaseVersion ) ) {
  76. if ( !this._createdBatches.has( historyDelta.batch ) ) {
  77. reversedDelta = transformDeltaSets( reversedDelta, [ historyDelta ], true ).deltasA;
  78. }
  79. }
  80. // 2. After reversed delta has been transformed by all history deltas, apply it.
  81. for ( let delta of reversedDelta ) {
  82. // Fix base version.
  83. delta.baseVersion = document.version;
  84. // Before applying, add the delta to the `redoingBatch`.
  85. redoingBatch.addDelta( delta );
  86. // Now, apply all operations of the delta.
  87. for ( let operation of delta.operations ) {
  88. document.applyOperation( operation );
  89. }
  90. }
  91. }
  92. }
  93. }