history.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module engine/model/history
  7. */
  8. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  9. /**
  10. * `History` keeps the track of all the deltas applied to the {@link module:engine/model/document~Document document}.
  11. */
  12. export default class History {
  13. /**
  14. * Creates an empty History instance.
  15. */
  16. constructor() {
  17. /**
  18. * Deltas added to the history.
  19. *
  20. * @protected
  21. * @member {Array.<module:engine/model/delta/delta~Delta>} module:engine/model/history~History#_deltas
  22. */
  23. this._deltas = [];
  24. /**
  25. * Helper structure that maps added delta's base version to the index in {@link module:engine/model/history~History#_deltas}
  26. * at which the delta was added.
  27. *
  28. * @protected
  29. * @member {Map} module:engine/model/history~History#_historyPoints
  30. */
  31. this._historyPoints = new Map();
  32. /**
  33. * Holds an information which {@link module:engine/model/delta/delta~Delta delta} undoes which
  34. * {@link module:engine/model/delta/delta~Delta delta}.
  35. *
  36. * Keys of the map are "undoing deltas", that is deltas that undone some other deltas. For each key, the
  37. * value is a delta that has been undone by the "undoing delta".
  38. *
  39. * @private
  40. * @member {Map} module:engine/model/history~History#_undoPairs
  41. */
  42. this._undoPairs = new Map();
  43. /**
  44. * Holds all undone deltas.
  45. *
  46. * @private
  47. * @member {Set.<module:engine/model/delta/delta~Delta>} module:engine/model/history~History#_undoneDeltas
  48. */
  49. this._undoneDeltas = new Set();
  50. }
  51. /**
  52. * Adds delta to the history.
  53. *
  54. * @param {module:engine/model/delta/delta~Delta} delta Delta to add.
  55. */
  56. addDelta( delta ) {
  57. if ( delta.operations.length > 0 && !this._historyPoints.has( delta.baseVersion ) ) {
  58. const index = this._deltas.length;
  59. this._deltas[ index ] = delta;
  60. this._historyPoints.set( delta.baseVersion, index );
  61. }
  62. }
  63. /**
  64. * Returns deltas added to the history.
  65. *
  66. * @param {Number} [from=0] Base version from which deltas should be returned (inclusive). Defaults to `0`, which means
  67. * that deltas from the first one will be returned.
  68. * @param {Number} [to=Number.POSITIVE_INFINITY] Base version up to which deltas should be returned (exclusive).
  69. * Defaults to `Number.POSITIVE_INFINITY` which means that deltas up to the last one will be returned.
  70. * @returns {Iterable.<module:engine/model/delta/delta~Delta>} Deltas added to the history from given base versions range.
  71. */
  72. * getDeltas( from = 0, to = Number.POSITIVE_INFINITY ) {
  73. // No deltas added, nothing to yield.
  74. if ( this._deltas.length === 0 ) {
  75. return;
  76. }
  77. // Will throw if base version is incorrect.
  78. let fromIndex = this._getIndex( from );
  79. // Base version is too low or too high and is not found in history.
  80. if ( fromIndex == -1 ) {
  81. return;
  82. }
  83. // We have correct `fromIndex` so let's iterate starting from it.
  84. while ( fromIndex < this._deltas.length ) {
  85. const delta = this._deltas[ fromIndex++ ];
  86. if ( delta.baseVersion >= to ) {
  87. break;
  88. }
  89. yield delta;
  90. }
  91. }
  92. /**
  93. * Returns delta from history that bases on given `baseVersion`.
  94. *
  95. * @param {Number} baseVersion Base version of the delta to get.
  96. * @returns {module:engine/model/delta/delta~Delta|null} Delta with given base version or `null` if there is no such delta in history.
  97. */
  98. getDelta( baseVersion ) {
  99. const index = this._historyPoints.get( baseVersion );
  100. return index === undefined ? null : this._deltas[ index ];
  101. }
  102. /**
  103. * Marks in history that one delta is a delta that is undoing the other delta. By marking deltas this way,
  104. * history is keeping more context information about deltas which helps in operational transformation.
  105. *
  106. * @param {module:engine/model/delta/delta~Delta} undoneDelta Delta which is undone by `undoingDelta`.
  107. * @param {module:engine/model/delta/delta~Delta} undoingDelta Delta which undoes `undoneDelta`.
  108. */
  109. setDeltaAsUndone( undoneDelta, undoingDelta ) {
  110. this._undoPairs.set( undoingDelta, undoneDelta );
  111. this._undoneDeltas.add( undoneDelta );
  112. }
  113. /**
  114. * Checks whether given `delta` is undoing by any other delta.
  115. *
  116. * @param {module:engine/model/delta/delta~Delta} delta Delta to check.
  117. * @returns {Boolean} `true` if given `delta` is undoing any other delta, `false` otherwise.
  118. */
  119. isUndoingDelta( delta ) {
  120. return this._undoPairs.has( delta );
  121. }
  122. /**
  123. * Checks whether given `delta` has been undone by any other delta.
  124. *
  125. * @param {module:engine/model/delta/delta~Delta} delta Delta to check.
  126. * @returns {Boolean} `true` if given `delta` has been undone any other delta, `false` otherwise.
  127. */
  128. isUndoneDelta( delta ) {
  129. return this._undoneDeltas.has( delta );
  130. }
  131. /**
  132. * For given `undoingDelta`, returns the delta which has been undone by it.
  133. *
  134. * @param {module:engine/model/delta/delta~Delta} undoingDelta
  135. * @returns {module:engine/model/delta/delta~Delta|undefined} Delta that has been undone by given `undoingDelta` or `undefined`
  136. * if given `undoingDelta` is not undoing any other delta.
  137. */
  138. getUndoneDelta( undoingDelta ) {
  139. return this._undoPairs.get( undoingDelta );
  140. }
  141. /**
  142. * Gets an index in {@link module:engine/model/history~History#_deltas} where delta with given `baseVersion` is added.
  143. *
  144. * @private
  145. * @param {Number} baseVersion Base version of delta.
  146. */
  147. _getIndex( baseVersion ) {
  148. const index = this._historyPoints.get( baseVersion );
  149. // Base version not found - it is either too high or too low, or is in the middle of delta.
  150. if ( index === undefined ) {
  151. const lastDelta = this._deltas[ this._deltas.length - 1 ];
  152. const nextBaseVersion = lastDelta.baseVersion + lastDelta.operations.length;
  153. if ( baseVersion < 0 || baseVersion >= nextBaseVersion ) {
  154. // Base version is too high or too low - it's acceptable situation.
  155. return -1;
  156. }
  157. /**
  158. * Given base version points to the middle of a delta.
  159. *
  160. * @error history-wrong-version
  161. */
  162. throw new CKEditorError( 'model-history-wrong-version: Given base version points to the middle of a delta.' );
  163. }
  164. return index;
  165. }
  166. }