r105274 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r105273‎ | r105274 | r105275 >
Date:01:52, 6 December 2011
Author:neilk
Status:deferred
Tags:
Comment:
Simplified transaction model, introduced isPartial for some deletes
Modified paths:
  • /trunk/extensions/VisualEditor/modules/es/models/es.SurfaceModel.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/views/es.SurfaceView.js (modified) (history)

Diff [purge]

Index: trunk/extensions/VisualEditor/modules/es/models/es.SurfaceModel.js
@@ -12,26 +12,22 @@
1313
1414 // Properties
1515 this.doc = doc;
16 - this.selection = new es.Range();
17 - this.states = [[]];
18 - this.initializeState( this.states.length - 1 );
 16+ this.selection = null;
 17+ this.history = [];
 18+ this.historyIndex = 0;
 19+ this.currentLengthDifference = 0;
1920
 21+ // TODO magic number move to configuration
2022 // Configuration
21 - this.distanceLimit = 24;
2223 this.lengthDifferenceLimit = 24;
 24+
 25+ // DEBUG don't commit
 26+ var _this = this;
 27+ this.addListener( 'transact', function() { console.log( _this.history ); } );
2328 };
2429
2530 /* Methods */
2631
27 -es.SurfaceModel.prototype.initializeState = function( stateIndex ) {
28 - if ( this.states[stateIndex] === undefined ) {
29 - throw 'Invalid state index error. State index our of range: ' + stateIndex;
30 - }
31 - this.currentStateIndex = stateIndex;
32 - this.currentState = this.states[stateIndex];
33 - this.currentStateDistance = 0;
34 - this.currentStateLengthDifference = 0;
35 -};
3632
3733 /**
3834 * Gets the document model of the surface.
@@ -44,10 +40,10 @@
4541 };
4642
4743 /**
48 - * Gets the selection for the current state.
 44+ * Gets the selection
4945 *
5046 * @method
51 - * @returns {es.Range} Current state's selection
 47+ * @returns {es.Range} Current selection
5248 */
5349 es.SurfaceModel.prototype.getSelection = function() {
5450 return this.selection;
@@ -57,34 +53,37 @@
5854 * Changes the selection.
5955 *
6056 * If changing the selection at a high frequency (such as while dragging) use the combine argument
61 - * to avoid them being split up into multiple states.
 57+ * to avoid them being split up into multiple history items
6258 *
6359 * @method
6460 * @param {es.Range} selection
65 - * @param {Boolean} combine Whether to prevent this transaction from causing a state push
 61+ * @param {Boolean} combine Whether to prevent this transaction from causing a history push
6662 */
67 -es.SurfaceModel.prototype.select = function( selection, combine ) {
 63+es.SurfaceModel.prototype.select = function( selection, isManual ) {
6864 selection.normalize();
69 - if ( !combine && this.shouldPushState( selection ) ) {
70 - this.pushState();
 65+
 66+ this.selection = selection;
 67+
 68+ if ( isManual ) {
 69+ // check if the last thing is a selection, if so, swap it.
 70+ this.pushSelection( selection );
7171 }
72 - // Filter out calls to select if they do not change the selection values
73 - var selectionChanged = !this.selection || (
74 - this.selection.from !== selection.from ||
75 - this.selection.to !== selection.to
76 - );
77 - if ( selectionChanged ) {
78 - var lastAction = this.states[this.states.length - 1];
79 - if ( lastAction instanceof es.Range ) {
80 - this.currentStateDistance += Math.abs(
81 - selection.from - this.states[this.states.length - 1].from
82 - );
83 - }
84 - this.currentState.push( selection );
85 - this.selection = selection;
86 - if ( selectionChanged ) {
87 - this.emit( 'select', this.selection.clone() );
88 - }
 72+
 73+ this.emit( 'select', this.selection.clone() );
 74+};
 75+
 76+/**
 77+ * Adds a selection (which is really just a marker for when we stop undo/redo) to the history.
 78+ * For the history, selections are just markers, so we don't want to record many of them in a row.
 79+ *
 80+ * @method
 81+ * @param {es.Range} selection
 82+ */
 83+es.SurfaceModel.prototype.pushSelection = function( selection ) {
 84+ if ( this.history[ this.history.length - 1 ] instanceof es.Range ) {
 85+ this.history[ this.history.length - 1 ] = selection;
 86+ } else {
 87+ this.history.push( selection );
8988 }
9089 };
9190
@@ -92,53 +91,81 @@
9392 * Applies a series of transactions to the content data.
9493 *
9594 * If committing multiple transactions which are the result of a single user action and need to be
96 - * part of a single state, use the combine argument for all but the last one to avoid them being
97 - * split up into multple states.
 95+ * part of a single history item, use the isPartial argument for all but the last one to avoid them being
 96+ * split up into multple history items.
9897 *
9998 * @method
10099 * @param {es.TransactionModel} transactions Tranasction to apply to the document
101 - * @param {Boolean} combine Whether to prevent this transaction from causing a state push
 100+ * @param {boolean} isPartial whether this transaction is part of a larger logical grouping of transactions
 101+ * (such as when replacing - delete, then insert)
102102 */
103 -es.SurfaceModel.prototype.transact = function( transaction, combine ) {
104 - if ( !combine && this.shouldPushState( transaction ) ) {
105 - this.pushState();
106 - }
107 - this.currentStateLengthDifference += transaction.getLengthDifference();
 103+es.SurfaceModel.prototype.transact = function( transaction, isPartial ) {
 104+ console.log( 'tx:' + $.map( transaction.getOperations(), function(tx) { return tx.type; } ).join(",")
 105+ + ' isPartial:' + isPartial );
108106 this.doc.commit( transaction );
109 - this.currentState.push( transaction );
 107+
 108+ // if we have changed the kind of operation (delete -> insert or insert -> delete or annotations )
 109+ // then push a new selection onto the history, to mark where the undo/redo should end.
 110+ var d = transaction.getLengthDifference();
 111+ if (
 112+ !isPartial &&
 113+ (
 114+ ( d === 0 ) ||
 115+ ( this.currentLengthDifference < 0 && d > 0 ) ||
 116+ ( this.currentLengthDifference > 0 && d < 0 ) ||
 117+ ( Math.abs( this.currentLengthDifference ) > this.lengthDifferenceLimit )
 118+ )
 119+ ) {
 120+ this.currentLengthDifference = d;
 121+ this.history.push( this.selection );
 122+ } else {
 123+ this.currentLengthDifference += d;
 124+ }
 125+
 126+ this.history.push( transaction );
 127+
110128 this.emit( 'transact', transaction );
111129 };
112130
 131+
113132 /**
114 - * Reverses one or more states.
115 - * We assume the user wants to undo some visible change to the document, so we only count
116 - * states that contain at least one transaction that changed the document somehow.
 133+ * Reverses one or more history items.
117134 *
118135 * @method
119 - * @param {Integer} Number of document-changing states to reverse
 136+ * @param {Integer} Number of history items to roll back
120137 */
121 -es.SurfaceModel.prototype.undo = function( transactionsToUndo ) {
 138+es.SurfaceModel.prototype.undo = function( statesToUndo ) {
 139+
 140+ console.log( 'about to undo...' );
 141+ console.log( this.states );
 142+ console.log( 'currentState: ' + this.currentState );
 143+ console.log( 'currentStateIndex: ' + this.currentStateIndex );
122144
123 - while ( transactionsToUndo ) {
124 - var hadTransaction = false;
 145+ lengthDifference = 0;
125146
126 - var state = this.currentState;
 147+ while ( statesToUndo ) {
 148+ statesToUndo--;
127149
128 - var i = state.length - 1;
129 - while ( i-- ) {
130 - if ( state[i] instanceof es.TransactionModel ) {
131 - hadTransaction = true;
132 - this.doc.rollback( state[i] );
 150+ if ( this.currentState.length ) {
 151+ for (var i = this.currentState.length - 1; i >= 0; i-- ) {
 152+ lengthDifference += this.currentState[i].getLengthDifference();
 153+ this.doc.rollback( this.currentState[i] );
133154 }
 155+ this.emit( 'undo', this.currentState );
134156 }
135 - this.emit( 'undo', state );
136157
137 - if ( hadTransaction ) {
138 - transactionsToUndo--;
139 - }
140158 // do we also want all the effects of initializeState? currentStateDistance to be 0, currentStateLengthDifference?
141 - this.initializeState( this.currentStateIndex - 1 );
 159+ if ( this.currentStateIndex > 0 ) {
 160+ this.initializeState( this.currentStateIndex - 1 );
 161+ }
142162 }
 163+
 164+ console.log( 'after undo...' );
 165+ console.log( this.states );
 166+ console.log( 'currentState: ' + this.currentState );
 167+ console.log( 'currentStateIndex: ' + this.currentStateIndex );
 168+
 169+ // TODO - make the appropriate selection now
143170 };
144171
145172 /**
@@ -152,107 +179,6 @@
153180 this.emit( 'redo'/*, transaction/selection*/ );
154181 };
155182
156 -/**
157 - * Checks if it's an appropriate time to push the state.
158 - *
159 - * @method
160 - * @returns {Boolean} Whether the state should be pushed
161 - */
162 -es.SurfaceModel.prototype.shouldPushState = function( nextAction ) {
163 - // Never push a new state if the current one is empty
164 - if ( !this.currentState.length ) {
165 - return false;
166 - }
167 - var lastAction = this.currentState[this.currentState.length - 1],
168 - nextDirection,
169 - lastDirection;
170 - if (
171 - // Check that types match
172 - nextAction instanceof es.Range && lastAction instanceof es.Range
173 - ) {
174 - if (
175 - // 2 or more select actions in a row are required to detect a direction
176 - this.states.length >= 2 && this.states[this.states.length - 2] instanceof es.Range
177 - ) {
178 - // Check we haven't changed directions
179 - lastDirection = this.states[this.states.length - 2].from - lastAction.from;
180 - nextDirection = lastAction.from - nextAction.from;
181 - if (
182 - // Both movements are in the same direction
183 - ( lastDirection < 0 && nextDirection < 0 ) ||
184 - ( lastDirection > 0 && nextDirection > 0 )
185 - ) {
186 - // Check we are still within the distance threshold
187 - if (
188 - Math.abs( nextAction.from - lastAction.from ) + this.currentStateDistance <
189 - this.distanceLimit
190 - ) {
191 - return false;
192 - }
193 - }
194 - }
195 - } else if (
196 - // Check that types match
197 - nextAction instanceof es.TransactionModel && lastAction instanceof es.TransactionModel
198 - ) {
199 - // Check if we've changed directions (insert vs remove)
200 - lastLengthDifference = lastAction.getLengthDifference();
201 - nextLengthDifference = nextAction.getLengthDifference();
202 - if (
203 - // Both movements are in the same direction
204 - ( lastLengthDifference < 0 && nextLengthDifference < 0 ) ||
205 - ( lastLengthDifference > 0 && nextLengthDifference > 0 )
206 - ) {
207 - // Check we are still within the length difference threshold
208 - if (
209 - nextLengthDifference + this.currentStateLengthDifference <
210 - this.lengthDifferenceLimit
211 - ) {
212 - return false;
213 - }
214 - }
215 - }
216 - return true;
217 -};
218 -
219 -/**
220 - * Removes any undone states and pushes a new state to the stack.
221 - *
222 - * @method
223 - */
224 -es.SurfaceModel.prototype.pushState = function() {
225 - // Automatically drop undone states - we are now moving in a new direction
226 - if ( this.states[this.states.length - 1] !== this.currentState ) {
227 - for ( var i = this.states.length - 1; i > this.currentStateIndex; i-- ) {
228 - this.emit( 'popState', this.states.pop() );
229 - }
230 - }
231 - // Push a new state to the stack
232 - this.optimizeState( this.states.length - 1 );
233 - this.states.push( [] );
234 - this.initializeState( this.states.length - 1 );
235 - this.emit( 'pushState' );
236 -};
237 -
238 -/**
239 - * Remove irrelevant selection actions from a given state
240 - * TODO: replace this with code to remove irrelevant selections as they are pushed
241 - * (for instance, between two insertions).
242 - * @param {Integer}
243 - */
244 -es.SurfaceModel.prototype.optimizeState = function( stateIndex ) {
245 - var skipSelects = false,
246 - newState = [];
247 - for ( var i = this.states[stateIndex].length - 1; i >= 0; i-- ) {
248 - var action = this.states[stateIndex][i];
249 - if ( !( action instanceof es.Range && skipSelects ) ) {
250 - newState.push( action );
251 - skipSelects = true;
252 - }
253 - }
254 - this.states[stateIndex] = newState;
255 -};
256 -
257183 /* Inheritance */
258184
259185 es.extendClass( es.SurfaceModel, es.EventEmitter );
Index: trunk/extensions/VisualEditor/modules/es/views/es.SurfaceView.js
@@ -321,7 +321,7 @@
322322 // Reset the initial left position
323323 this.cursor.initialLeft = null;
324324 // Apply new selection
325 - this.model.select( selection );
 325+ this.model.select( selection, true );
326326 }
327327 // If the inut isn't already focused, focus it and select it's contents
328328 if ( !this.$input.is( ':focus' ) ) {
@@ -381,7 +381,7 @@
382382 es.SurfaceView.prototype.onMouseUp = function( e ) {
383383 if ( e.which === 1 ) { // left mouse button
384384 this.mouse.selectingMode = this.mouse.selectedRange = null;
385 - this.model.select( this.currentSelection );
 385+ this.model.select( this.currentSelection, true );
386386 // We have to manually call this because the selection will not have changed between the
387387 // most recent mousemove and this mouseup
388388 this.contextView.set();
@@ -544,7 +544,7 @@
545545 return true;
546546 };
547547
548 -es.SurfaceView.prototype.handleDelete = function( backspace ) {
 548+es.SurfaceView.prototype.handleDelete = function( backspace, isPartial ) {
549549 var selection = this.currentSelection.clone(),
550550 sourceOffset,
551551 targetOffset,
@@ -575,7 +575,7 @@
576576 }
577577
578578 selection.from = selection.to = targetOffset;
579 - this.model.select( selection, true );
 579+ this.model.select( selection );
580580
581581 if ( sourceNode === targetNode ||
582582 ( typeof sourceSplitableNode !== 'undefined' &&
@@ -583,12 +583,12 @@
584584 tx = this.model.getDocument().prepareRemoval(
585585 new es.Range( targetOffset, sourceOffset )
586586 );
587 - this.model.transact( tx, true );
 587+ this.model.transact( tx, isPartial );
588588 } else {
589589 tx = this.model.getDocument().prepareInsertion(
590590 targetOffset, sourceNode.model.getContentData()
591591 );
592 - this.model.transact( tx, true );
 592+ this.model.transact( tx, isPartial );
593593
594594 var nodeToDelete = sourceNode;
595595 es.DocumentNode.traverseUpstream( nodeToDelete, function( node ) {
@@ -603,14 +603,14 @@
604604 range.from = this.documentView.getOffsetFromNode( nodeToDelete, false );
605605 range.to = range.from + nodeToDelete.getElementLength();
606606 tx = this.model.getDocument().prepareRemoval( range );
607 - this.model.transact( tx, true );
 607+ this.model.transact( tx, isPartial );
608608 }
609609 } else {
610610 // selection removal
611611 tx = this.model.getDocument().prepareRemoval( selection );
612 - this.model.transact( tx, true );
 612+ this.model.transact( tx, isPartial );
613613 selection.from = selection.to = selection.start;
614 - this.model.select( selection, true );
 614+ this.model.select( selection );
615615 }
616616 };
617617
@@ -618,7 +618,7 @@
619619 var selection = this.currentSelection.clone(),
620620 tx;
621621 if ( selection.from !== selection.to ) {
622 - this.handleDelete();
 622+ this.handleDelete( false, true );
623623 }
624624 var node = this.documentView.getNodeFromOffset( selection.to, false ),
625625 nodeOffset = this.documentView.getOffsetFromNode( node, false );
@@ -631,7 +631,7 @@
632632 nodeOffset + node.getElementLength(),
633633 [ { 'type': 'paragraph' }, { 'type': '/paragraph' } ]
634634 );
635 - this.model.transact( tx, true );
 635+ this.model.transact( tx );
636636 selection.from = selection.to = nodeOffset + node.getElementLength() + 1;
637637 } else {
638638 var stack = [],
@@ -658,11 +658,11 @@
659659 return true;
660660 } );
661661 tx = this.documentView.model.prepareInsertion( selection.to, stack );
662 - this.model.transact( tx, true );
 662+ this.model.transact( tx );
663663 selection.from = selection.to =
664664 this.model.getDocument().getRelativeContentOffset( selection.to, 1 );
665665 }
666 - this.model.select( selection, true );
 666+ this.model.select( selection );
667667 };
668668
669669 es.SurfaceView.prototype.insertFromInput = function() {
@@ -697,12 +697,12 @@
698698 var data = val.split('');
699699 es.DocumentModel.addAnnotationsToData( data, this.getInsertionAnnotations() );
700700 tx = this.model.getDocument().prepareInsertion( selection.from, data );
701 - this.model.transact( tx, true );
 701+ this.model.transact( tx );
702702
703703 // Move the selection
704704 selection.from += val.length;
705705 selection.to += val.length;
706 - this.model.select( selection, true );
 706+ this.model.select( selection );
707707 }
708708 };
709709
@@ -832,7 +832,7 @@
833833 } else {
834834 selection.from = selection.to = to;
835835 }
836 - this.model.select( selection );
 836+ this.model.select( selection, true );
837837 };
838838
839839 /**

Status & tagging log