overleaf/services/web/public/coffee/editor/sharejs/client/ace.coffee
2014-02-12 10:23:40 +00:00

128 lines
3.4 KiB
CoffeeScript

# This is some utility code to connect an ace editor to a sharejs document.
Range = require("ace/range").Range
# Convert an ace delta into an op understood by share.js
applyToShareJS = (editorDoc, delta, doc) ->
# Get the start position of the range, in no. of characters
getStartOffsetPosition = (range) ->
# This is quite inefficient - getLines makes a copy of the entire
# lines array in the document. It would be nice if we could just
# access them directly.
lines = editorDoc.getLines 0, range.start.row
offset = 0
for line, i in lines
offset += if i < range.start.row
line.length
else
range.start.column
# Add the row number to include newlines.
offset + range.start.row
pos = getStartOffsetPosition(delta.range)
switch delta.action
when 'insertText' then doc.insert pos, delta.text
when 'removeText' then doc.del pos, delta.text.length
when 'insertLines'
text = delta.lines.join('\n') + '\n'
doc.insert pos, text
when 'removeLines'
text = delta.lines.join('\n') + '\n'
doc.del pos, text.length
else throw new Error "unknown action: #{delta.action}"
return
# Attach an ace 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).
window.sharejs.extendDoc 'attach_ace', (editor, keepEditorContents) ->
throw new Error 'Only text documents can be attached to ace' unless @provides['text']
doc = this
editorDoc = editor.getSession().getDocument()
editorDoc.setNewLineMode 'unix'
check = ->
window.setTimeout ->
editorText = editorDoc.getValue()
otText = doc.getText()
if editorText != otText
console.error "Text does not match!"
console.error "editor: #{editorText}"
console.error "ot: #{otText}"
# Should probably also replace the editor text with the doc snapshot.
, 0
if keepEditorContents
doc.del 0, doc.getText().length
doc.insert 0, editorDoc.getValue()
else
editorDoc.setValue doc.getText()
check()
# When we apply ops from sharejs, ace emits edit events. We need to ignore those
# to prevent an infinite typing loop.
suppress = false
# Listen for edits in ace
editorListener = (change) ->
return if suppress
applyToShareJS editorDoc, change.data, doc
check()
editorDoc.on 'change', editorListener
# Listen for remote ops on the sharejs document
docListener = (op) ->
suppress = true
applyToDoc editorDoc, op
suppress = false
check()
# Horribly inefficient.
offsetToPos = (offset) ->
# Again, very inefficient.
lines = editorDoc.getAllLines()
row = 0
for line, row in lines
break if offset <= line.length
# +1 for the newline.
offset -= lines[row].length + 1
row:row, column:offset
doc.on 'insert', (pos, text) ->
suppress = true
editorDoc.insert offsetToPos(pos), text
suppress = false
check()
doc.on 'delete', (pos, text) ->
suppress = true
range = Range.fromPoints offsetToPos(pos), offsetToPos(pos + text.length)
editorDoc.remove range
suppress = false
check()
doc.detach_ace = ->
doc.removeListener 'remoteop', docListener
editorDoc.removeListener 'change', editorListener
delete doc.detach_ace
return