-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathdoc-undo.js
144 lines (121 loc) · 4.91 KB
/
doc-undo.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//noinspection JSLint
var g = typeof global === 'undefined' ? window : global;
//noinspection JSLint,JSHint,ThisExpressionReferencesGlobalObjectJS
(function (papyrus) {
require('./spcf');
var spcf = papyrus.spcf;
var Doc = papyrus.Doc;
//TODO prod: what size limit editStack should have?
var UNDO_STACK_SIZE_LIMIT = 50;
Doc._p.undo = function () { doUndoRedo(this, false); };
Doc._p.redo = function () { doUndoRedo(this, true); };
function initDocUndoRedo(doc) {
//TODO think: what about using svbuf for stack?
doc.undoData = {
eventStackUndo: [],
eventStackRedo: [],
undoRedoInProgress: false,
editFlag: false,
cursorMoved: false,
lastCursorPosition: null
};
}
function doUndoRedo(doc, isRedo) {
var undoData = doc.undoData;
var fromStack, toStack;
if (isRedo) {
fromStack = undoData.eventStackRedo;
toStack = undoData.eventStackUndo;
} else {
fromStack = undoData.eventStackUndo;
toStack = undoData.eventStackRedo;
}
if (undoData.undoRedoInProgress) { return; }
if (!fromStack.length) { return; } //nothing to undo/redo
undoData.undoRedoInProgress = true;
doc.disableEditing();
try {
var eventOps = fromStack.pop();
var antiOps = [];
//generate anti-operations
while (eventOps.length) {
var sv = eventOps.pop();
Array.prototype.push.apply(antiOps, doc.getAntiOperations(sv));
}
antiOps.cursor = doc.get('.se');
doc.set('.se', eventOps.cursor);
//apply anti-ops
var i, len;
for (i = 0, len = antiOps.length; i < len; i++) {
var anti = antiOps[i];
var id = doc.set(anti.spec, anti.val);
if (spcf.has(anti.spec, '!')) {
anti.spec = spcf.replace(anti.spec, '!00', spcf.get(id, '!'));
}
}
//add anti-ops for possible redo
toStack.push(antiOps);
} finally {
undoData.undoRedoInProgress = false;
doc.enableEditing();
doc.reset('.se');
notifyUndoRedoStatus(doc);
}
}
function pushNextEditEvent(spec, val) {
var doc = this;
var undoData = doc.undoData;
//skip if position not actually changed
if (undoData.lastCursorPosition === val) { return; }
undoData.lastCursorPosition = val;
//skip cursor movements caused by text-editing
if (undoData.editFlag) {
undoData.editFlag = false;
return;
}
undoData.cursorMoved = true;
}
function notifyUndoRedoStatus(doc) {
var undoData = doc.undoData;
papyrus.emit('undoStackChanged', doc, undoData.eventStackUndo.length, undoData.eventStackRedo.length);
}
function addEventToEditHistory(spec, val) {
var doc = this;
var undoData = doc.undoData;
if (undoData.undoRedoInProgress) { return; } //skip events generated during undo/redo
var serial = spcf.getParsed(spec, '!');
if (!serial || serial.ssn !== doc.ssn) { return; } //not this session changes
undoData.editFlag = true;
spec = spcf.as(spec);
var type = spcf.type(spec);
if (undoData.cursorMoved || //after moving cursor
!undoData.eventStackUndo.length || //when undo-stack is empty
type === '.at' || //on formatting
type === '.ma' || type === '.md' || //on media add/del
(type === '.in' && (val === ' ' || val === '\n'))) {//on "spaces"
undoData.eventStackUndo.push([]); //create new undo-stack item
}
var eventOps = undoData.eventStackUndo[undoData.eventStackUndo.length - 1];
if (!eventOps.length) {
// save cursor where it was before insert
eventOps.cursor = doc.get('.se');
}
eventOps.push({spec: spec, val: val});
//not allow redo after input been made
undoData.eventStackRedo = [];
undoData.cursorMoved = false;
if (undoData.eventStackUndo.length > UNDO_STACK_SIZE_LIMIT) {
undoData.eventStackUndo.splice(0, undoData.eventStackUndo.length - UNDO_STACK_SIZE_LIMIT);
}
notifyUndoRedoStatus(doc);
}
papyrus.addEventToEditHistory = addEventToEditHistory;
papyrus.on('docInit', initDocUndoRedo);
papyrus.addFilter('.se', pushNextEditEvent);
papyrus.addFilter('.at', addEventToEditHistory);
papyrus.addFilter('.in', addEventToEditHistory);
papyrus.addFilter('.rm', addEventToEditHistory);
papyrus.addFilter('.ud', addEventToEditHistory);
papyrus.addFilter('.ma', addEventToEditHistory);
papyrus.addFilter('.md', addEventToEditHistory);
}(g['papyrus'] = g['papyrus'] || {}));