Skip to content

Interacting with the Tandem Agent

Sameer Chitley edited this page Mar 10, 2018 · 2 revisions

Plugin Prerequisites

Important: Before writing a new Tandem editor plugin, make sure the editor provides a way for your plugin to do the following (at a minimum):

  1. Spawn a process, manage its lifecycle, and communicate with it via stdin and stdout.
  2. Be notified when the editor's text buffer is changed by the user (e.g. the ability to subscribe to text changed events).
  3. Atomically make changes to the editor's text buffer.

Without this, the plugin will not be able to correctly react to both buffer changes initiated by the user nor to buffer changes initiated by remote participants.

Interacting with the Tandem Agent

Connecting

The plugin can instruct the agent to either create a Tandem session or join an existing session.

  • To create a new session, the plugin should send the agent the HostSession message. The plugin will respond back with the SessionInfo message, containing the session ID.
  • To join an existing session, the plugin should send the agent the JoinSession message containing the session ID.

No acknowledgement is sent back to the plugin when the connection is established; it can just assume that the connection succeeded.

Reporting Local Writes

Changes that the local participant makes to the editor's buffer must be reported to the agent in order to have them sent to all remote participants. This is done by sending the agent a NewPatches message with a list of patches.

Each patch should have the form (when serialized to JSON):

{
    "start": {"row": <row>, "column": <col>},
    "end": {"row": <row>, "column": <col>},
    "text": <text>
}

The row and column correspond to the line and column in the text buffer at its state before the patch was applied.

When changing a range of text, start indicates the start position and end indicates one index past the end position. The length of the string under text must be the same length as the range specified by start and end.

To insert text, the start position should be the location where the text is inserted and end should be set to {"row": 0, "column": 0}. The text property should be set to the string that was inserted.

To delete text, the start position should indicate the start position of the range to delete and end should indicate one index past the range's end position. The text property should be set to an empty string "".

The patches sent in the message should be ordered in the order they should be applied (i.e. patch at index 0 is applied first, then patch at index 1, and so on).

No acknowledgement is sent back by the agent upon receipt of the NewPatches message. The plugin can assume it was received and processed successfully (i.e. fire and forget).

Applying Remote Changes

A three message exchange with the agent is used to apply remote changes: WriteRequest, WriteRequestAck, and finally ApplyPatches.

When remote changes are received by the agent, the agent will send the plugin a WriteRequest message. The plugin does not have to handle this message immediately, but it should make an effort to process the message soon to ensure remote changes are applied without too much of a delay.

When the plugin is ready to accept the remote changes, it should reply to the agent with a WriteRequestAck message. The plugin should send this message with the same sequence number as the WriteRequest it received (this is only used for debugging purposes right now). Before sending this message, the plugin should ensure:

  • All changes to the editor's local buffer have been reported to the agent.
  • No further changes will be made to the editor's local buffer by the local participant until the plugin receives the ApplyPatches message.

For correctness, it is very important that these two points are true before the acknowledgement message is sent. In practice, this means when the plugin processes the WriteRequest message, it should:

  • Grab some kind of write lock for the editor's buffer.
  • Report all changes in the local buffer to the agent.
  • Then send the acknowledgement message and wait for a response (while still holding the write lock).

Upon receiving the acknowledgement message, the agent will reply with an ApplyPatches message that contains the remote changes that should be applied to the local buffer. Once these changes are applied to the local buffer, the plugin should resume allowing local writes to the buffer.

The ApplyPatches message will contain a list of patches to be applied in the order they should be applied (i.e. the patch at index 0 should be applied first, then index 1, and so on).

Each patch will have the form (in serialized JSON):

{
  "oldStart": {"row": <row>, "column": <col>},
  "oldEnd": {"row": <row>, "column": <col>},
  "oldText": <old text>,
  "newStart": {"row": <row>, "column": <col>},
  "newEnd": {"row": <row>, "column": <col>},
  "newText": <new text>
}

Transport and Serialization

The plugin should communicate with the agent using stdin and stdout. The agent will write messages to stdout and the plugin should send messages to the agent by writing to the agent's stdin.

Messages are serialized as JSON strings and are separated by new line characters. This means the serialized messages cannot be formatted with new lines (all new lines must be escaped). See agent/tandem/protocol/editor/messages.py for the serialization format.