Merge pull request #3356 from overleaf/jpa-unified-remote-flag

[frontend] sharejs: replace 'suppress' hack w/ unified 'remote' flag

GitOrigin-RevId: 25e761a1e543d08477d9b0f3960ddf055d38790b
This commit is contained in:
Jakob Ackermann 2020-11-12 10:11:54 +01:00 committed by Copybot
parent 4680af4b7d
commit 0f2e5542e0
4 changed files with 36 additions and 51 deletions

View file

@ -244,7 +244,7 @@ class AutoCompleteManager {
// NOTE: this is also the case when a user backspaces over a highlighted
// region
if (
!change.remote &&
change.origin !== 'remote' &&
change.action === 'insert' &&
end.row === cursorPosition.row &&
end.column === cursorPosition.column + 1

View file

@ -34,7 +34,7 @@ export default (MetadataManager = class MetadataManager {
this.debouncer = {} // DocId => Timeout
const onChange = change => {
if (change.remote) {
if (change.origin === 'remote') {
return
}
if (!['remove', 'insert'].includes(change.action)) {

View file

@ -14,7 +14,7 @@ class UndoManager {
}
onChange(change) {
if (!change.remote) return
if (change.origin !== '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

View file

@ -1477,7 +1477,7 @@ define(['ace/ace','crypto-js/sha1'], function (_ignore, CryptoJSSHA1) {
var editorDoc = editor.getSession().getDocument();
editorDoc.setNewLineMode('unix');
var check = function check() {
function check() {
return window.setTimeout(function () {
var editorText = editorDoc.getValue();
var otText = doc.getText();
@ -1493,22 +1493,15 @@ define(['ace/ace','crypto-js/sha1'], function (_ignore, CryptoJSSHA1) {
, 0);
};
if (keepEditorContents) {
doc.del(0, doc.getText().length);
doc.insert(0, editorDoc.getValue());
} else {
editorDoc.setValue(doc.getText());
}
onDelete(0, editorDoc.getValue());
onInsert(0, doc.getText());
check();
// When we apply ops from sharejs, ace emits edit events. We need to ignore those
// to prevent an infinite typing loop.
var suppress = false;
// Listen for edits in ace
var editorListener = function editorListener(change) {
if (suppress) {
function editorListener(change) {
if (change.origin === 'remote') {
// this change has been injected via sharejs
return;
}
@ -1527,7 +1520,7 @@ define(['ace/ace','crypto-js/sha1'], function (_ignore, CryptoJSSHA1) {
editorDoc.on('change', editorListener);
// Horribly inefficient.
var offsetToPos = function offsetToPos(offset) {
function offsetToPos(offset) {
// Again, very inefficient.
var lines = editorDoc.getAllLines();
@ -1545,12 +1538,13 @@ define(['ace/ace','crypto-js/sha1'], function (_ignore, CryptoJSSHA1) {
return { row: row, column: offset };
};
// We want to insert a remote:true into the delta if the op comes from the
// underlying sharejs doc (which means it is from a remote op), so we have to do
// the work of editorDoc.insert and editorDoc.remove manually. These methods are
// copied from ace.js doc#insert and #remove, and then inject the remote:true
// flag into the delta.
var onInsert = function onInsert(pos, text) {
// We want to insert the flag `origin: 'remote'` into the delta if the op
// is the initial document write or comes from the underlying sharejs doc
// (which means it is from a remote op), so we have to do the work of
// editorDoc.insert and editorDoc.remove manually.
// These methods are copied from ace.js doc#insert and #remove, and then
// inject the `origin: 'remote'` flag into the delta.
function onInsert(pos, text) {
if (editorDoc.getLength() <= 1) {
editorDoc.$detectNewLine(text);
}
@ -1563,31 +1557,27 @@ define(['ace/ace','crypto-js/sha1'], function (_ignore, CryptoJSSHA1) {
column: (lines.length === 1 ? start.column : 0) + lines[lines.length - 1].length
};
suppress = true;
editorDoc.applyDelta({
start: start,
end: end,
action: 'insert',
lines: lines,
remote: true
origin: 'remote'
});
suppress = false;
return check();
};
var onDelete = function onDelete(pos, text) {
function onDelete(pos, text) {
var range = Range.fromPoints(offsetToPos(pos), offsetToPos(pos + text.length));
var start = editorDoc.clippedPos(range.start.row, range.start.column);
var end = editorDoc.clippedPos(range.end.row, range.end.column);
suppress = true;
editorDoc.applyDelta({
start: start,
end: end,
action: 'remove',
lines: editorDoc.getLinesForRange({ start: start, end: end }),
remote: true
origin: 'remote'
});
suppress = false;
return check();
};
@ -1632,6 +1622,11 @@ define(['ace/ace','crypto-js/sha1'], function (_ignore, CryptoJSSHA1) {
// Attach a CodeMirror editor to the document. The editor's contents are replaced
// with the document's contents unless keepEditorContents is true. (In which case
// the document's contents are nuked and replaced with the editor's).
// NOTE: When upgrading CM, make sure to check for new special cases of
// origin prefixes as documented for `doc.setSelection`. We are using
// a custom `origin: 'remote'` which may conflict.
// Perma link of the docs at the time of writing this note:
// https://web.archive.org/web/20201029163528/https://codemirror.net/doc/manual.html#selection_origin
window.sharejs.extendDoc('attach_cm', function (editor, keepEditorContents) {
if (!this.provides.text) {
throw new Error('Only text documents can be attached to CodeMirror2');
@ -1640,7 +1635,7 @@ define(['ace/ace','crypto-js/sha1'], function (_ignore, CryptoJSSHA1) {
var sharedoc = this;
var editorDoc = editor.getDoc();
var check = function check() {
function check() {
return window.setTimeout(function () {
var editorText = editor.getValue();
var otText = sharedoc.getText();
@ -1658,22 +1653,15 @@ define(['ace/ace','crypto-js/sha1'], function (_ignore, CryptoJSSHA1) {
, 0);
};
if (keepEditorContents) {
this.del(0, sharedoc.getText().length);
this.insert(0, editor.getValue());
} else {
editor.setValue(sharedoc.getText());
}
onDelete(0, editor.getValue());
onInsert(0, sharedoc.getText());
check();
// When we apply ops from sharejs, CodeMirror emits edit events.
// We need to ignore those to prevent an infinite typing loop.
var suppress = false;
// Listen for edits in CodeMirror.
var editorListener = function editorListener(ed, change) {
if (suppress) {
function editorListener(ed, change) {
if (change.origin === 'remote') {
// this change has been injected via sharejs
return;
}
var fromUndo = (change.origin === 'undo')
@ -1683,26 +1671,23 @@ define(['ace/ace','crypto-js/sha1'], function (_ignore, CryptoJSSHA1) {
editorDoc.on('change', editorListener);
var onInsert = function onInsert(pos, text) {
suppress = true;
function onInsert(pos, text) {
// All the primitives we need are already in CM's API.
editor.replaceRange(text, editor.posFromIndex(pos));
// call signature: editor.replaceRange(text, from, to, origin)
editor.replaceRange(text, editor.posFromIndex(pos), undefined, 'remote');
// 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;
return check();
};
var onDelete = function onDelete(pos, text) {
suppress = true;
function onDelete(pos, text) {
var from = editor.posFromIndex(pos);
var to = editor.posFromIndex(pos + text.length);
editor.replaceRange('', from, to);
editor.replaceRange('', from, to, 'remote');
// 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;
return check();
};