mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 06:33:55 +01:00
Merge pull request #1689 from sharelatex/revert-1684-revert-1637-as-clear-undo-stack-remote-edit
Bring back clearing of undo stack when remote change is received GitOrigin-RevId: f18b1b4c1af0ab0c8563c3670c73d75afa6d8345
This commit is contained in:
committed by
sharelatex
parent
9808eb7f55
commit
d2de6d8e87
@@ -1696,6 +1696,9 @@ define(['ace/ace','libs/sha1'], function () {
|
|||||||
suppress = true;
|
suppress = true;
|
||||||
// All the primitives we need are already in CM's API.
|
// All the primitives we need are already in CM's API.
|
||||||
editor.replaceRange(text, editor.posFromIndex(pos));
|
editor.replaceRange(text, editor.posFromIndex(pos));
|
||||||
|
// Clear CM's undo/redo history on remote edit. This prevents issues where
|
||||||
|
// a user can accidentally remove another user's edits
|
||||||
|
editor.clearHistory();
|
||||||
suppress = false;
|
suppress = false;
|
||||||
return check();
|
return check();
|
||||||
};
|
};
|
||||||
@@ -1705,6 +1708,9 @@ define(['ace/ace','libs/sha1'], function () {
|
|||||||
var from = editor.posFromIndex(pos);
|
var from = editor.posFromIndex(pos);
|
||||||
var to = editor.posFromIndex(pos + text.length);
|
var to = editor.posFromIndex(pos + text.length);
|
||||||
editor.replaceRange('', from, to);
|
editor.replaceRange('', from, to);
|
||||||
|
// Clear CM's undo/redo history on remote edit. This prevents issues where
|
||||||
|
// a user can accidentally remove another user's edits
|
||||||
|
editor.clearHistory()
|
||||||
suppress = false;
|
suppress = false;
|
||||||
return check();
|
return check();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
const undoManager = new UndoManager(scope, editor, element)
|
const undoManager = new UndoManager(editor)
|
||||||
const highlightsManager = new HighlightsManager(scope, editor, element)
|
const highlightsManager = new HighlightsManager(scope, editor, element)
|
||||||
const cursorPositionManager = new CursorPositionManager(
|
const cursorPositionManager = new CursorPositionManager(
|
||||||
scope,
|
scope,
|
||||||
@@ -643,6 +643,18 @@ define([
|
|||||||
editor.renderer.off('resize', trackChangesManager.onResize)
|
editor.renderer.off('resize', trackChangesManager.onResize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initUndo = function() {
|
||||||
|
// Emulate onChangeSession event. Note: listening to changeSession
|
||||||
|
// event is unnecessary since this method is called when we switch
|
||||||
|
// sessions (via ShareJS changing) anyway
|
||||||
|
undoManager.onChangeSession(editor.getSession())
|
||||||
|
editor.on('change', undoManager.onChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tearDownUndo = function() {
|
||||||
|
editor.off('change', undoManager.onChange)
|
||||||
|
}
|
||||||
|
|
||||||
const onSessionChangeForCursorPosition = function(e) {
|
const onSessionChangeForCursorPosition = function(e) {
|
||||||
if (e.oldSession != null) {
|
if (e.oldSession != null) {
|
||||||
e.oldSession.selection.off(
|
e.oldSession.selection.off(
|
||||||
@@ -744,6 +756,7 @@ define([
|
|||||||
triggerEditorInitEvent()
|
triggerEditorInitEvent()
|
||||||
initSpellCheck()
|
initSpellCheck()
|
||||||
initTrackChanges()
|
initTrackChanges()
|
||||||
|
initUndo()
|
||||||
|
|
||||||
resetScrollMargins()
|
resetScrollMargins()
|
||||||
|
|
||||||
@@ -794,6 +807,7 @@ define([
|
|||||||
var detachFromAce = function(sharejs_doc) {
|
var detachFromAce = function(sharejs_doc) {
|
||||||
tearDownSpellCheck()
|
tearDownSpellCheck()
|
||||||
tearDownTrackChanges()
|
tearDownTrackChanges()
|
||||||
|
tearDownUndo()
|
||||||
sharejs_doc.detachFromAce()
|
sharejs_doc.detachFromAce()
|
||||||
sharejs_doc.off('remoteop.recordRemote')
|
sharejs_doc.off('remoteop.recordRemote')
|
||||||
|
|
||||||
@@ -823,6 +837,7 @@ define([
|
|||||||
scope.$broadcast('changeEditor')
|
scope.$broadcast('changeEditor')
|
||||||
tearDownSpellCheck()
|
tearDownSpellCheck()
|
||||||
tearDownCursorPosition()
|
tearDownCursorPosition()
|
||||||
|
tearDownUndo()
|
||||||
detachFromAce(scope.sharejsDoc)
|
detachFromAce(scope.sharejsDoc)
|
||||||
const session = editor.getSession()
|
const session = editor.getSession()
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
|
|||||||
@@ -1,537 +1,34 @@
|
|||||||
/* eslint-disable
|
|
||||||
max-len,
|
|
||||||
no-return-assign,
|
|
||||||
no-throw-literal,
|
|
||||||
no-undef,
|
|
||||||
no-unused-vars,
|
|
||||||
*/
|
|
||||||
// TODO: This file was created by bulk-decaffeinate.
|
|
||||||
// Fix any style issues and re-enable lint.
|
|
||||||
/*
|
|
||||||
* decaffeinate suggestions:
|
|
||||||
* DS101: Remove unnecessary use of Array.from
|
|
||||||
* DS102: Remove unnecessary code created because of implicit returns
|
|
||||||
* DS205: Consider reworking code to avoid use of IIFEs
|
|
||||||
* DS207: Consider shorter variations of null checks
|
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
||||||
*/
|
|
||||||
define(['ace/ace'], function() {
|
define(['ace/ace'], function() {
|
||||||
let UndoManager
|
const BuiltInUndoManager = ace.require('ace/undomanager').UndoManager
|
||||||
const { Range } = ace.require('ace/range')
|
|
||||||
const { EditSession } = ace.require('ace/edit_session')
|
|
||||||
const Doc = ace.require('ace/document').Document
|
|
||||||
|
|
||||||
return (UndoManager = class UndoManager {
|
class UndoManager {
|
||||||
constructor($scope, editor) {
|
constructor(editor) {
|
||||||
this.$scope = $scope
|
|
||||||
this.editor = editor
|
this.editor = editor
|
||||||
this.$scope.undo = { show_remote_warning: false }
|
|
||||||
|
|
||||||
this.reset()
|
this.onChangeSession = this.onChangeSession.bind(this)
|
||||||
|
this.onChange = this.onChange.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
this.editor.on('changeSession', e => {
|
onChangeSession(session) {
|
||||||
this.reset()
|
session.setUndoManager(new BuiltInUndoManager())
|
||||||
this.session = e.session
|
}
|
||||||
return e.session.setUndoManager(this)
|
|
||||||
|
onChange(change) {
|
||||||
|
if (!change.remote) return
|
||||||
|
|
||||||
|
// HACK: remote changes in Ace are added by the ShareJS/Ace adapter
|
||||||
|
// asynchronously via a timeout (see attach_ace function). This makes it
|
||||||
|
// impossible to clear to undo stack when remote changes are received.
|
||||||
|
// To hack around this we queue the undo stack clear so that it applies
|
||||||
|
// after the change is applied
|
||||||
|
setTimeout(() => {
|
||||||
|
this.editor
|
||||||
|
.getSession()
|
||||||
|
.getUndoManager()
|
||||||
|
.reset()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
showUndoConflictWarning() {
|
return UndoManager
|
||||||
this.$scope.$apply(() => {
|
|
||||||
return (this.$scope.undo.show_remote_warning = true)
|
|
||||||
})
|
|
||||||
|
|
||||||
return setTimeout(() => {
|
|
||||||
return this.$scope.$apply(() => {
|
|
||||||
return (this.$scope.undo.show_remote_warning = false)
|
|
||||||
})
|
|
||||||
}, 4000)
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.firstUpdate = true
|
|
||||||
this.undoStack = []
|
|
||||||
return (this.redoStack = [])
|
|
||||||
}
|
|
||||||
|
|
||||||
execute(options) {
|
|
||||||
let newDeltaSet
|
|
||||||
if (this.firstUpdate) {
|
|
||||||
// The first update we receive is Ace setting the document, which we should
|
|
||||||
// ignore
|
|
||||||
this.firstUpdate = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const aceDeltaSets = options.args[0]
|
|
||||||
if (aceDeltaSets == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.session = options.args[1]
|
|
||||||
|
|
||||||
// We need to split the delta sets into local or remote groups before pushing onto
|
|
||||||
// the undo stack, since these are treated differently.
|
|
||||||
const splitDeltaSets = []
|
|
||||||
let currentDeltaSet = null // Make global to this function
|
|
||||||
;(newDeltaSet = function() {
|
|
||||||
currentDeltaSet = { group: 'doc', deltas: [] }
|
|
||||||
return splitDeltaSets.push(currentDeltaSet)
|
|
||||||
})()
|
|
||||||
const currentRemoteState = null
|
|
||||||
|
|
||||||
for (var deltaSet of Array.from(aceDeltaSets || [])) {
|
|
||||||
if (deltaSet.group === 'doc') {
|
|
||||||
// ignore code folding etc.
|
|
||||||
for (let delta of Array.from(deltaSet.deltas)) {
|
|
||||||
if (
|
|
||||||
currentDeltaSet.remote != null &&
|
|
||||||
currentDeltaSet.remote !== !!delta.remote
|
|
||||||
) {
|
|
||||||
newDeltaSet()
|
|
||||||
}
|
|
||||||
currentDeltaSet.deltas.push(delta)
|
|
||||||
currentDeltaSet.remote = !!delta.remote
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The lines are currently as they are after applying all these deltas, but to turn into simple deltas,
|
|
||||||
// we need the lines before each delta group.
|
|
||||||
let docLines = this.session.getDocument().getAllLines()
|
|
||||||
docLines = this._revertAceDeltaSetsOnDocLines(aceDeltaSets, docLines)
|
|
||||||
for (deltaSet of Array.from(splitDeltaSets)) {
|
|
||||||
var simpleDeltaSet
|
|
||||||
;({ simpleDeltaSet, docLines } = this._aceDeltaSetToSimpleDeltaSet(
|
|
||||||
deltaSet,
|
|
||||||
docLines
|
|
||||||
))
|
|
||||||
const frame = {
|
|
||||||
deltaSets: [simpleDeltaSet],
|
|
||||||
remote: deltaSet.remote
|
|
||||||
}
|
|
||||||
this.undoStack.push(frame)
|
|
||||||
}
|
|
||||||
return (this.redoStack = [])
|
|
||||||
}
|
|
||||||
|
|
||||||
undo(dontSelect) {
|
|
||||||
// We rely on the doclines being in sync with the undo stack, so make sure
|
|
||||||
// any pending undo deltas are processed.
|
|
||||||
this.session.$syncInformUndoManager()
|
|
||||||
|
|
||||||
const localUpdatesMade = this._shiftLocalChangeToTopOfUndoStack()
|
|
||||||
if (!localUpdatesMade) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const update = this.undoStack.pop()
|
|
||||||
if (update == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update.remote) {
|
|
||||||
this.showUndoConflictWarning()
|
|
||||||
}
|
|
||||||
|
|
||||||
const lines = this.session.getDocument().getAllLines()
|
|
||||||
const linesBeforeDelta = this._revertSimpleDeltaSetsOnDocLines(
|
|
||||||
update.deltaSets,
|
|
||||||
lines
|
|
||||||
)
|
|
||||||
const deltaSets = this._simpleDeltaSetsToAceDeltaSets(
|
|
||||||
update.deltaSets,
|
|
||||||
linesBeforeDelta
|
|
||||||
)
|
|
||||||
const selectionRange = this.session.undoChanges(deltaSets, dontSelect)
|
|
||||||
this.redoStack.push(update)
|
|
||||||
return selectionRange
|
|
||||||
}
|
|
||||||
|
|
||||||
redo(dontSelect) {
|
|
||||||
const update = this.redoStack.pop()
|
|
||||||
if (update == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const lines = this.session.getDocument().getAllLines()
|
|
||||||
const deltaSets = this._simpleDeltaSetsToAceDeltaSets(
|
|
||||||
update.deltaSets,
|
|
||||||
lines
|
|
||||||
)
|
|
||||||
const selectionRange = this.session.redoChanges(deltaSets, dontSelect)
|
|
||||||
this.undoStack.push(update)
|
|
||||||
return selectionRange
|
|
||||||
}
|
|
||||||
|
|
||||||
_shiftLocalChangeToTopOfUndoStack() {
|
|
||||||
const head = []
|
|
||||||
let localChangeExists = false
|
|
||||||
while (this.undoStack.length > 0) {
|
|
||||||
const update = this.undoStack.pop()
|
|
||||||
head.unshift(update)
|
|
||||||
if (!update.remote) {
|
|
||||||
localChangeExists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!localChangeExists) {
|
|
||||||
this.undoStack = this.undoStack.concat(head)
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
// Undo stack looks like undoStack ++ reorderedhead ++ head
|
|
||||||
// Reordered head starts of empty and consumes entries from head
|
|
||||||
// while keeping the localChange at the top for as long as it can
|
|
||||||
let localChange = head.shift()
|
|
||||||
const reorderedHead = [localChange]
|
|
||||||
while (head.length > 0) {
|
|
||||||
const remoteChange = head.shift()
|
|
||||||
localChange = reorderedHead.pop()
|
|
||||||
const result = this._swapSimpleDeltaSetsOrder(
|
|
||||||
localChange.deltaSets,
|
|
||||||
remoteChange.deltaSets
|
|
||||||
)
|
|
||||||
if (result != null) {
|
|
||||||
remoteChange.deltaSets = result[0]
|
|
||||||
localChange.deltaSets = result[1]
|
|
||||||
reorderedHead.push(remoteChange)
|
|
||||||
reorderedHead.push(localChange)
|
|
||||||
} else {
|
|
||||||
reorderedHead.push(localChange)
|
|
||||||
reorderedHead.push(remoteChange)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.undoStack = this.undoStack.concat(reorderedHead).concat(head)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_swapSimpleDeltaSetsOrder(firstDeltaSets, secondDeltaSets) {
|
|
||||||
const newFirstDeltaSets = this._copyDeltaSets(firstDeltaSets)
|
|
||||||
const newSecondDeltaSets = this._copyDeltaSets(secondDeltaSets)
|
|
||||||
for (let firstDeltaSet of Array.from(
|
|
||||||
newFirstDeltaSets.slice(0).reverse()
|
|
||||||
)) {
|
|
||||||
for (let firstDelta of Array.from(
|
|
||||||
firstDeltaSet.deltas.slice(0).reverse()
|
|
||||||
)) {
|
|
||||||
for (let secondDeltaSet of Array.from(newSecondDeltaSets)) {
|
|
||||||
for (let secondDelta of Array.from(secondDeltaSet.deltas)) {
|
|
||||||
const success = this._swapSimpleDeltaOrderInPlace(
|
|
||||||
firstDelta,
|
|
||||||
secondDelta
|
|
||||||
)
|
|
||||||
if (!success) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [newSecondDeltaSets, newFirstDeltaSets]
|
|
||||||
}
|
|
||||||
|
|
||||||
_copyDeltaSets(deltaSets) {
|
|
||||||
const newDeltaSets = []
|
|
||||||
for (let deltaSet of Array.from(deltaSets)) {
|
|
||||||
const newDeltaSet = {
|
|
||||||
deltas: [],
|
|
||||||
group: deltaSet.group
|
|
||||||
}
|
|
||||||
newDeltaSets.push(newDeltaSet)
|
|
||||||
for (let delta of Array.from(deltaSet.deltas)) {
|
|
||||||
const newDelta = { position: delta.position }
|
|
||||||
if (delta.insert != null) {
|
|
||||||
newDelta.insert = delta.insert
|
|
||||||
}
|
|
||||||
if (delta.remove != null) {
|
|
||||||
newDelta.remove = delta.remove
|
|
||||||
}
|
|
||||||
newDeltaSet.deltas.push(newDelta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newDeltaSets
|
|
||||||
}
|
|
||||||
|
|
||||||
_swapSimpleDeltaOrderInPlace(firstDelta, secondDelta) {
|
|
||||||
const result = this._swapSimpleDeltaOrder(firstDelta, secondDelta)
|
|
||||||
if (result == null) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
firstDelta.position = result[1].position
|
|
||||||
secondDelta.position = result[0].position
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
_swapSimpleDeltaOrder(firstDelta, secondDelta) {
|
|
||||||
if (firstDelta.insert != null && secondDelta.insert != null) {
|
|
||||||
if (
|
|
||||||
secondDelta.position >=
|
|
||||||
firstDelta.position + firstDelta.insert.length
|
|
||||||
) {
|
|
||||||
secondDelta.position -= firstDelta.insert.length
|
|
||||||
return [secondDelta, firstDelta]
|
|
||||||
} else if (secondDelta.position > firstDelta.position) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
firstDelta.position += secondDelta.insert.length
|
|
||||||
return [secondDelta, firstDelta]
|
|
||||||
}
|
|
||||||
} else if (firstDelta.remove != null && secondDelta.remove != null) {
|
|
||||||
if (secondDelta.position >= firstDelta.position) {
|
|
||||||
secondDelta.position += firstDelta.remove.length
|
|
||||||
return [secondDelta, firstDelta]
|
|
||||||
} else if (
|
|
||||||
secondDelta.position + secondDelta.remove.length >
|
|
||||||
firstDelta.position
|
|
||||||
) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
firstDelta.position -= secondDelta.remove.length
|
|
||||||
return [secondDelta, firstDelta]
|
|
||||||
}
|
|
||||||
} else if (firstDelta.insert != null && secondDelta.remove != null) {
|
|
||||||
if (
|
|
||||||
secondDelta.position >=
|
|
||||||
firstDelta.position + firstDelta.insert.length
|
|
||||||
) {
|
|
||||||
secondDelta.position -= firstDelta.insert.length
|
|
||||||
return [secondDelta, firstDelta]
|
|
||||||
} else if (
|
|
||||||
secondDelta.position + secondDelta.remove.length >
|
|
||||||
firstDelta.position
|
|
||||||
) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
firstDelta.position -= secondDelta.remove.length
|
|
||||||
return [secondDelta, firstDelta]
|
|
||||||
}
|
|
||||||
} else if (firstDelta.remove != null && secondDelta.insert != null) {
|
|
||||||
if (secondDelta.position >= firstDelta.position) {
|
|
||||||
secondDelta.position += firstDelta.remove.length
|
|
||||||
return [secondDelta, firstDelta]
|
|
||||||
} else {
|
|
||||||
firstDelta.position += secondDelta.insert.length
|
|
||||||
return [secondDelta, firstDelta]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw 'Unknown delta types'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_applyAceDeltasToDocLines(deltas, docLines) {
|
|
||||||
const doc = new Doc(docLines.join('\n'))
|
|
||||||
doc.applyDeltas(deltas)
|
|
||||||
return doc.getAllLines()
|
|
||||||
}
|
|
||||||
|
|
||||||
_revertAceDeltaSetsOnDocLines(deltaSets, docLines) {
|
|
||||||
const session = new EditSession(docLines.join('\n'))
|
|
||||||
session.undoChanges(deltaSets)
|
|
||||||
return session.getDocument().getAllLines()
|
|
||||||
}
|
|
||||||
|
|
||||||
_revertSimpleDeltaSetsOnDocLines(deltaSets, docLines) {
|
|
||||||
let doc = docLines.join('\n')
|
|
||||||
for (let deltaSet of Array.from(deltaSets.slice(0).reverse())) {
|
|
||||||
for (let delta of Array.from(deltaSet.deltas.slice(0).reverse())) {
|
|
||||||
if (delta.remove != null) {
|
|
||||||
doc =
|
|
||||||
doc.slice(0, delta.position) +
|
|
||||||
delta.remove +
|
|
||||||
doc.slice(delta.position)
|
|
||||||
} else if (delta.insert != null) {
|
|
||||||
doc =
|
|
||||||
doc.slice(0, delta.position) +
|
|
||||||
doc.slice(delta.position + delta.insert.length)
|
|
||||||
} else {
|
|
||||||
throw 'Unknown delta type'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return doc.split('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
_aceDeltaSetToSimpleDeltaSet(deltaSet, docLines) {
|
|
||||||
const simpleDeltas = []
|
|
||||||
for (let delta of Array.from(deltaSet.deltas)) {
|
|
||||||
simpleDeltas.push(this._aceDeltaToSimpleDelta(delta, docLines))
|
|
||||||
docLines = this._applyAceDeltasToDocLines([delta], docLines)
|
|
||||||
}
|
|
||||||
const simpleDeltaSet = {
|
|
||||||
deltas: simpleDeltas,
|
|
||||||
group: deltaSet.group
|
|
||||||
}
|
|
||||||
return { simpleDeltaSet, docLines }
|
|
||||||
}
|
|
||||||
|
|
||||||
_simpleDeltaSetsToAceDeltaSets(simpleDeltaSets, docLines) {
|
|
||||||
return (() => {
|
|
||||||
const result = []
|
|
||||||
for (let deltaSet of Array.from(simpleDeltaSets)) {
|
|
||||||
let aceDeltas = []
|
|
||||||
for (let delta of Array.from(deltaSet.deltas)) {
|
|
||||||
const newAceDeltas = this._simpleDeltaToAceDeltas(delta, docLines)
|
|
||||||
docLines = this._applyAceDeltasToDocLines(newAceDeltas, docLines)
|
|
||||||
aceDeltas = aceDeltas.concat(newAceDeltas)
|
|
||||||
}
|
|
||||||
result.push({
|
|
||||||
deltas: aceDeltas,
|
|
||||||
group: deltaSet.group
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
|
|
||||||
_aceDeltaToSimpleDelta(aceDelta, docLines) {
|
|
||||||
let simpleDelta
|
|
||||||
const { start } = aceDelta
|
|
||||||
if (start == null) {
|
|
||||||
const JSONstringifyWithCycles = function(o) {
|
|
||||||
const seen = []
|
|
||||||
return JSON.stringify(o, function(k, v) {
|
|
||||||
if (typeof v === 'object') {
|
|
||||||
if (seen.indexOf(v) >= 0) {
|
|
||||||
return '__cycle__'
|
|
||||||
}
|
|
||||||
seen.push(v)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const error = new Error(
|
|
||||||
`aceDelta had no start event: ${JSONstringifyWithCycles(aceDelta)}`
|
|
||||||
)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
const linesBefore = docLines.slice(0, start.row)
|
|
||||||
const position =
|
|
||||||
linesBefore.join('').length + // full lines
|
|
||||||
linesBefore.length + // new line characters
|
|
||||||
start.column // partial line
|
|
||||||
switch (aceDelta.action) {
|
|
||||||
case 'insert':
|
|
||||||
simpleDelta = {
|
|
||||||
position,
|
|
||||||
insert: aceDelta.lines.join('\n')
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'remove':
|
|
||||||
simpleDelta = {
|
|
||||||
position,
|
|
||||||
remove: aceDelta.lines.join('\n')
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown Ace action: ${aceDelta.action}`)
|
|
||||||
}
|
|
||||||
return simpleDelta
|
|
||||||
}
|
|
||||||
|
|
||||||
_simplePositionToAcePosition(position, docLines) {
|
|
||||||
let column = 0
|
|
||||||
let row = 0
|
|
||||||
for (let line of Array.from(docLines)) {
|
|
||||||
if (position > line.length) {
|
|
||||||
row++
|
|
||||||
position -= (line + '\n').length
|
|
||||||
} else {
|
|
||||||
column = position
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { row, column }
|
|
||||||
}
|
|
||||||
|
|
||||||
_simpleDeltaToAceDeltas(simpleDelta, docLines) {
|
|
||||||
let aceDelta, end
|
|
||||||
const { row, column } = this._simplePositionToAcePosition(
|
|
||||||
simpleDelta.position,
|
|
||||||
docLines
|
|
||||||
)
|
|
||||||
|
|
||||||
const lines = (simpleDelta.insert || simpleDelta.remove || '').split('\n')
|
|
||||||
|
|
||||||
const start = { column, row }
|
|
||||||
if (lines.length > 1) {
|
|
||||||
end = {
|
|
||||||
row: row + lines.length - 1,
|
|
||||||
column: lines[lines.length - 1].length
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
end = {
|
|
||||||
row,
|
|
||||||
column: column + lines[0].length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (simpleDelta.insert != null) {
|
|
||||||
aceDelta = {
|
|
||||||
action: 'insert',
|
|
||||||
lines,
|
|
||||||
start,
|
|
||||||
end
|
|
||||||
}
|
|
||||||
} else if (simpleDelta.remove != null) {
|
|
||||||
aceDelta = {
|
|
||||||
action: 'remove',
|
|
||||||
lines,
|
|
||||||
start,
|
|
||||||
end
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw `Unknown simple delta: ${simpleDelta}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return [aceDelta]
|
|
||||||
}
|
|
||||||
|
|
||||||
_concatSimpleDeltas(deltas) {
|
|
||||||
if (deltas.length === 0) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const concattedDeltas = []
|
|
||||||
let previousDelta = deltas.shift()
|
|
||||||
for (let delta of Array.from(deltas)) {
|
|
||||||
if (delta.insert != null && previousDelta.insert != null) {
|
|
||||||
if (
|
|
||||||
previousDelta.position + previousDelta.insert.length ===
|
|
||||||
delta.position
|
|
||||||
) {
|
|
||||||
previousDelta = {
|
|
||||||
insert: previousDelta.insert + delta.insert,
|
|
||||||
position: previousDelta.position
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
concattedDeltas.push(previousDelta)
|
|
||||||
previousDelta = delta
|
|
||||||
}
|
|
||||||
} else if (delta.remove != null && previousDelta.remove != null) {
|
|
||||||
if (previousDelta.position === delta.position) {
|
|
||||||
previousDelta = {
|
|
||||||
remove: previousDelta.remove + delta.remove,
|
|
||||||
position: delta.position
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
concattedDeltas.push(previousDelta)
|
|
||||||
previousDelta = delta
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
concattedDeltas.push(previousDelta)
|
|
||||||
previousDelta = delta
|
|
||||||
}
|
|
||||||
}
|
|
||||||
concattedDeltas.push(previousDelta)
|
|
||||||
|
|
||||||
return concattedDeltas
|
|
||||||
}
|
|
||||||
|
|
||||||
hasUndo() {
|
|
||||||
return this.undoStack.length > 0
|
|
||||||
}
|
|
||||||
hasRedo() {
|
|
||||||
return this.redoStack.length > 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user