2014-02-12 05:23:40 -05:00
|
|
|
# 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
|
|
|
|
|
2014-06-24 10:31:44 -04:00
|
|
|
# MODIFIED by James: We will set the doc contents ourselves to
|
|
|
|
# avoid an extra entry in the undo stack.
|
|
|
|
# if keepEditorContents
|
|
|
|
# doc.del 0, doc.getText().length
|
|
|
|
# doc.insert 0, editorDoc.getValue()
|
|
|
|
# else
|
|
|
|
# editorDoc.setValue doc.getText()
|
2014-02-12 05:23:40 -05:00
|
|
|
|
|
|
|
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
|
|
|
|
|