Skip to content

Instantly share code, notes, and snippets.

@geakstr
Last active March 25, 2016 12:30
Show Gist options
  • Save geakstr/6c8f3644dbd126d63164 to your computer and use it in GitHub Desktop.
Save geakstr/6c8f3644dbd126d63164 to your computer and use it in GitHub Desktop.
Ace editor and ShareJS text adapter
function attachAce(editor, doc) {
if (!doc.provides.text) {
throw new Error("Cannot attach ace to non-text share document");
}
let suppress = false;
editor.setValue(doc.getSnapshot() || "");
check();
const newline = editor.getDocument().getNewLineCharacter();
const newlineLength = newline.length;
const countChar = (lines) => {
let cnt = 0;
for (let i = 0, l = lines.length; i < l; i++) {
cnt += lines[i].length;
}
return cnt + lines.length * newlineLength;
};
const indexToPosition = (index) => {
let line = "";
const len = editor.getLength();
for (let i = 0; i < len; i++) {
line = editor.getLine(i);
index -= line.length + newlineLength;
if (index < 0)
return {
row: i,
column: index + line.length + newlineLength
};
}
return {
row: len - 1,
column: line.length
};
};
doc.on("op", (op, local) => {
if (!local) {
suppress = true;
let retain = 0;
for (let i = 0, l = op.length; i < l; i += 1) {
const x = op[i];
if (is.integer(x)) {
retain += x;
} else if (is.string(x)) {
editor.insert(indexToPosition(retain), x);
retain += x.length;
} else {
editor.remove({
start: indexToPosition(retain),
end: indexToPosition(retain + x.d)
});
}
}
suppress = false;
check();
}
});
const onLocalChange = (change) => {
if (suppress) {
return;
}
applyToShareJS(change);
check();
}
editor.on("change", onLocalChange);
editor.detachShareJsDoc = () => {
doc.onRemove = null;
doc.onInsert = null;
editor.off("change", onLocalChange);
}
function applyToShareJS(data) {
let startPos = 0;
if (data.start.row - 1 >= 0) {
startPos = countChar(editor.getLines(0, data.start.row - 1));
}
startPos += data.start.column;
const len = data.lines.length;
if (data.action === "insert" && len === 1) {
doc.submitOp([startPos, data.lines[0]]);
} else if (data.action === "insert" && len > 1) {
let text = "";
for (let i = 0; i < len; i++) {
text += data.lines[i] + (i === len - 1 ? "" : newline);
}
doc.submitOp([startPos, text]);
} else if (data.action === "remove" && len === 1) {
doc.submitOp([startPos, {
d: data.lines[0].length
}]);
} else if (data.action === "remove" && len > 1) {
let cnt = 0;
for (let i = 0; i < len; i++) {
cnt += data.lines[i].length + (i === 0 ? 0 : newlineLength);
}
doc.submitOp([startPos, {
d: cnt
}]);
} else {
console.error(`Unknown action: ${data.action}`);
}
}
function check() {
setTimeout(function() {
const editorText = editor.getValue();
const shareText = doc.getSnapshot() || "";
if (editorText !== shareText) {
console.error("Editor and share text does not match!");
let similar = "";
for (let i = 0; i < Math.min(editorText.length, shareText.length); i += 1) {
if (editorText[i] === shareText[i]) {
similar += editorText[i];
} else {
console.error(`Similar: ${similar}`);
console.error(`Diff: editor: ${editorText[i]}; share: ${shareText[i]}`);
break;
}
}
suppress = true;
const range = editor.getSelection().getRange();
editor.setValue(shareText);
editor.getUndoManager().reset();
editor.getSelection().setSelectionRange(range);
suppress = false;
}
}, 0);
}
return doc;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment