Skip to content

Instantly share code, notes, and snippets.

@geakstr
Last active January 6, 2016 13:34
Show Gist options
  • Save geakstr/86d996aa8a8d69771eb1 to your computer and use it in GitHub Desktop.
Save geakstr/86d996aa8a8d69771eb1 to your computer and use it in GitHub Desktop.
Ace editor and ShareJS json adapter
/*global BCSocket*/
import "script!browserchannel/dist/bcsocket";
import is from "utils/is";
import sharejs from "share/lib/client";
function connect(address) {
return new sharejs.Connection(new BCSocket(address, {
origin: "*",
crossdomainXhr: true,
reconnect: true
}));
}
function toText(doc, newline) {
const lines = doc.getSnapshot().lines;
let text = "";
for (let i = 0, l = lines.length; i < l; i += 1) {
text += lines[i].text + (i === l - 1 ? "" : newline);
}
return text;
}
function attachAce(editor, doc) {
const newline = editor.getDocument().getNewLineCharacter();
let suppress = false;
editor.setValue(toText(doc, newline));
check();
doc.on("op", (ops, local) => {
if (!local) {
try {
suppress = true;
for (let i = 0, l = ops.length; i < l; i += 1) {
const op = ops[i];
const p = op.p;
if (!is.undefined(op.si)) { // Insert into string
editor.insert({
row: p[1],
column: p[3]
}, op.si);
} else if (!is.undefined(op.sd)) { // Remove from string
editor.remove({
start: {
row: p[1],
column: p[3]
},
end: {
row: p[1],
column: p[3] + op.sd.length
}
});
} else if (!is.undefined(op.li)) { // Insert line
const startRow = p[1] - 1;
editor.insert({
row: startRow,
column: editor.getLine(startRow).length
}, (startRow < 0 ? "" : "\n") + op.li.text + (startRow < 0 ? "\n" : ""));
} else if (!is.undefined(op.ld)) { // Remove line
const startRow = Math.max(0, p[1] - 1);
editor.remove({
start: {
row: startRow,
column: editor.getLine(startRow).length
},
end: {
row: p[1],
column: editor.getLine(p[1]).length
}
});
}
}
suppress = false;
} catch(e) {
console.log(e);
} finally {
check();
}
}
});
function onLocalChange(change) {
if (suppress) {
return;
}
try {
applyToShareJS(change);
} catch(e) {
console.log(e);
} finally {
check();
}
}
editor.on("change", onLocalChange);
editor.detachShareJsDoc = () => {
doc.onRemove = null;
doc.onInsert = null;
editor.off("change", onLocalChange);
}
function insertBlock(idx, text) {
return {
p: ["lines", idx],
li: {
"text": text
}
};
}
function removeBlock(idx, obj) {
return {
p: ["lines", idx],
ld: obj
};
}
function removeText(idx, offset, text) {
return {
p: ["lines", idx, "text", offset],
sd: text
};
}
function insertText(idx, offset, text) {
return {
p: ["lines", idx, "text", offset],
si: text
};
}
function applyToShareJS(data, enter) {
const len = editor.getLength();
const startIdx = data.start.row;
const endIdx = data.end.row;
const startPos = data.start.column;
const splited = data.lines;
const splitedLen = splited.length;
const ops = [];
if (data.action === "insert") {
if (splitedLen === 1) { // Insert single line
ops.push(insertText(startIdx, startPos, splited[0]));
} else { // Insert multiline
if (startIdx === 0 && startPos === 0) { // Into head
let i = 0;
for (i = 0; i < splitedLen - 1; i += 1) {
ops.push(insertBlock(i, splited[i]));
}
ops.push(insertText(i, 0, data.lines[splitedLen - 1]));
} else if (startIdx === len - 1 && startPos === editor.getLine(len - 1).length) { // Into tail
ops.push(insertText(startIdx, startPos, splited[0]));
for (let i = 1, j = len; i < splitedLen; i += 1, j += 1) {
ops.push(insertBlock(j, splited[i]));
}
} else { // Into body
const moved = editor.getLine(endIdx).substring(splited[splitedLen - 1].length);
ops.push(removeText(startIdx, startPos, moved));
ops.push(insertText(startIdx, startPos, splited[0]));
let j = startIdx + 1;
for (let i = 1; i < splitedLen - 1; i += 1, j += 1) {
ops.push(insertBlock(j, splited[i]));
}
ops.push(insertBlock(j, splited[splitedLen - 1] + moved));
}
}
} else if (data.action === "remove") {
ops.push(removeText(startIdx, startPos, splited[0]));
if (splitedLen > 1) {
const moved = editor.getLine(startIdx).substring(startPos);
ops.push(insertText(startIdx, startPos, moved));
const idx = startIdx + 1;
for (let i = 1; i < splitedLen - 1; i += 1) {
ops.push(removeBlock(idx, {
text: splited[i]
}));
}
ops.push(removeBlock(idx, {
text: splited[splitedLen - 1] + moved
}));
}
}
doc.submitOp(ops);
}
function check() {
const editorText = editor.getValue();
const shareText = toText(doc, newline);
if (editorText !== shareText) {
console.error("Editor and share text does not match!");
console.log("Editor:", editorText);
console.log("Share:", shareText);
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;
}
}
return doc;
}
export default function sharer(address, collectionName, docName, editor) {
const connection = connect(address);
const doc = connection.get(collectionName, docName);
doc.subscribe();
doc.whenReady(() => {
if (!doc.type || doc.type.name !== "json0") {
doc.create("json0", {
lines: [{
text: "Данную статью можно считать вольным переводом (хотя скорее попыткой разобраться) данной статьи. И да, написанна она скорее для математиков, нежели для широкой аудитории."
}, {
text: ""
}, {
text: "- Небольшой спойлер: в начале это казалось мне какой-то магией, но потом я понял подвох…"
}, {
text: ""
}, {
text: "В наши дни машина Тьюринга (далее МТ) — универсальное определение понятия алгоритма, а значит и универсальное определение «решателя задач». Существует множество других моделей алгоритма — лямбда исчисление, алгорифмы Маркова и т.д., но все они математически эквивалентны МТ, так что хоть они и интересны, но в теоретическом мире ничего существенно не меняют."
}, {
text: ""
}, {
text: "Вообще говоря, есть другие модели — Недетерминированная машина Тьюринга, Квантовые машины Тьюринга. Однако они (пока) являются только абстрактными моделиями, не реализуемые на практике."
}]
});
}
if (doc.type && doc.type.name === "json0") {
attachAce(editor.getSession(), doc);
}
});
return {
connection: connection,
doc: doc
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment