How to setText() for TextBuffer without emitting a didChange event?


#1

Hi, everyone

I’m new to atom. I’m trying to write a collaborative programming tool (different users modify a file simultaneously online) for atom.

In my package, I’d like to call setTextInRange() for a TextBuffer object. But I don’t want this setTextInRange to trigger a didChange event. How can I achieve this? (or alternatively, how can I identify whether the change is did by the user or by the package in the onChange callback).

Thanks. YangZX


#2

To my knowledge, there is no way to change the text without a change event firing. That’s the point of the event, to let every subscriber know whenever there is any change.

It sounds like this is an X/Y problem. Perhaps you can describe what it is you’re trying to do ultimately.


#3

Thanks for your reply.

In my package, several users (for example, Alice and Bob) edit a file at the same time online. Whenever a user modifies the text (e.g. Alice appends a ‘hello world’), the package sends the change to other users (Bob). I want other users (Bob) apply this change in his local TextBuffer. However, if I use setTextInRange() to apply this change, this change will trigger a didChange event for this user (Bob). Then the didChange callback will treat the change as the user’s action and send the change to other users(Alice) to ask them to apply the change again…

I think I should identify whether the change is did by the user or by the package. Currently, I set a bool flag after the setTextInRange() and skip the didChange callback if the flag is true. But I think this method is not reliable since I can’t guarantee the next didChange event is the one triggered by the setTextInRange()

see the code below:

console.log 'BirdwayAtom was toggled!'
if @watching == false
  @watching = true
  @socketlog = fs.openSync '/tmp/socketlog', 'w'

  # watch the text change event
  if editor = atom.workspace.getActiveTextEditor()
    if buffer = editor.getBuffer()
      @handler = buffer.onDidChange (diff) =>
        if BirdwayAtom.skip
          console.log "skipped"
          console.log "skip = false"
          BirdwayAtom.skip = false
          return null;
        console.log "change:"
        for k,v of diff
          console.log k + ":" + v
        console.log ""

        jsondata = JSON.stringify diff
        fs.writeSync @socketlog, (jsondata + "\n")
        @sock.write (jsondata + "\n")

  # open the socket to the client
  @sock = new net.Socket()

  @sock.on 'error', (err)=>
    console.log ("socket error: " + err)
    @watching = false
    fs.closeSync @socketlog
    @handler.dispose()

  # @sock.on 'data', (data) =>
  #   console.log data
  carrier.carry(@sock).on 'line', (line)=>
    console.log "got one line:" + line
    try
      diff = JSON.parse line
    catch error
      console.log error
    cBuffer = atom.workspace.getActiveTextEditor().getBuffer()
    console.log "skip = true"
    BirdwayAtom.skip = true
    cBuffer.setTextInRange diff.oldRange, diff.newText


  @sock.connect 9527, '127.0.0.1', ->
    console.log "connected to local client"

#4

I can see a couple methods of solving this without breaking this fundamental contract in Atom’s API:

  • You could send a hash of the file before and after the change along with the change information. If the file already matches the end-state hash, then you know that change was already applied and it can be discarded.
  • You could send a hash of the change information along with the change information. Then each participant could keep a list of recent change hashes. If it is in the list, discard it.

#5

The first method seems to be a good idea. A hash of the text can also help to synchronize between participants.

Thanks for your help.