Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Atom] Create Atom Plugin #93

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
15 changes: 15 additions & 0 deletions plugins/atom/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = {
Copy link
Member

@jamiboym jamiboym Feb 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep the styling for all our .js files the same and all our .py files the same, and so on - I believe the crdt styling doesn't change any rules from airbnb's default

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Some of these should be here, like the globals, since they are atom specific.
Others like the rules can be moved to a global config if you're okay with that:

  • no-underscore-dangle means you can't have members like this._privateMember which I am against since thats a convention we follow in the python library as well and is generally well accepted javascript
  • no-plusplus seems reasonable to have, value++ is common and unless you have a good reason why we shouldn't allow it I don't see why not
  • 'no-linely-ifit should be okay to have anifinside anelse` statement. I think the linter is giving a false positive for me, but even if it isn't Personally I don't see why not.

I'm okay for removing any of these configs. as long as they are given/within reason. But I agree that they should be kept consistent, and we should leave them configs in the tandem root.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an if inside an else is just an else if isn't it? For the others, sure -- but I think we should move the.eslintrc in the Tandem root folder and symlink it to both the crdt folder and here

"extends": "airbnb-base",
"plugins": [
"import"
],
"globals": {
"atom": false,
"document": false,
},
"rules": {
"no-underscore-dangle": "off",
"no-plusplus": "off",
"no-lonely-if": "off"
}
};
5 changes: 5 additions & 0 deletions plugins/atom/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.DS_Store
npm-debug.log
node_modules
agent
crdt
28 changes: 28 additions & 0 deletions plugins/atom/atom_dev_setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#! /bin/bash

SCRIPTPATH=$( cd $(dirname $0) ; pwd -P )

UNINSTALL_OPTION="--uninstall"

function install() {
rm -f agent
rm -f crdt

ln -s $SCRIPTPATH/../../agent $SCRIPTPATH
ln -s $SCRIPTPATH/../../crdt $SCRIPTPATH

apm link .
}

function uninstall() {
rm -f agent
rm -f crdt

apm unlink .
}

if [ "$1" == "$UNINSTALL_OPTION" ] ; then
uninstall
else
install
fi
58 changes: 58 additions & 0 deletions plugins/atom/lib/connectView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use babel';

import { CompositeDisposable, Disposable } from 'atom';

const makeButton = (text, callback) => {
const button = document.createElement('button');
button.classList.add('btn');
button.textContent = text;
button.addEventListener('click', callback);
return button;
};

export default class ConnectView {
constructor(connectCallback, closeCallback) {
this._element = document.createElement('div');
this._element.classList.add('tandem-connect-view');

const label = document.createElement('div');
const labelText = document.createElement('span');
labelText.textContent = 'Enter an IP and port (e.g. localhost 12345)';
label.appendChild(labelText);
this._element.appendChild(label);

const input = document.createElement('input');
input.classList.add('input-text');
this._element.appendChild(input);

const okButtonCallback = () => {
connectCallback(input.value);
input.value = '';
};
const okButton = makeButton('Connect', okButtonCallback);

const cancelButtonCallback = () => {
closeCallback();
input.value = '';
};
const cancelButton = makeButton('Cancel', cancelButtonCallback);

this._element.appendChild(okButton);
this._element.appendChild(cancelButton);

this._subscriptions = new CompositeDisposable();
this._subscriptions.add(new Disposable(() => {
okButton.removeEventListener('click', okButtonCallback);
cancelButton.removeEventListener('click', cancelButtonCallback);
}));
}

destroy() {
this.subscriptions.dispose();
this._element.remove();
}

getElement() {
return this._element;
}
}
176 changes: 176 additions & 0 deletions plugins/atom/lib/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
'use babel';

const EditorProtocolMessageType = Object.freeze({
ApplyText: 'apply-text',
ApplyPatches: 'apply-patches',
CheckDocumentSync: 'check-document-sync',
ConnectTo: 'connect-to',
WriteRequest: 'write-request',
WriteRequestAck: 'write-request-ack',
NewPatches: 'new-patches',
UserChangedEditorText: 'user-changed-editor-text',
});

class ConnectTo {
constructor(host, port) {
this._type = EditorProtocolMessageType.ConnectTo;
this._host = host;
this._port = port;
}

getType() {
return this._type;
}

toPayload() {
return {
host: this._host,
port: this._port,
};
}

static fromPayload(payload) {
return new ConnectTo(payload.host, payload.port);
}
}

class NewPatches {
constructor(patchList) {
this._type = EditorProtocolMessageType.NewPatches;
this._patchList = patchList;
}

getType() {
return this._type;
}

toPayload() {
return {
patch_list: this._patchList,
};
}

static fromPayload(payload) {
return new NewPatches(payload.patchList);
}
}

class ApplyPatches {
constructor(patchList) {
this._type = EditorProtocolMessageType.ApplyPatches;
this._patchList = patchList;
}

getType() {
return this._type;
}

getPatchList() {
return this._patchList;
}

toPayload() {
return {
patch_list: this._patchList,
};
}

static fromPayload(payload) {
return new ApplyPatches(payload.patch_list);
}
}

class WriteRequest {
constructor(seq) {
this._type = EditorProtocolMessageType.WriteRequest;
this._seq = seq;
}

getType() {
return this._type;
}

getSeq() {
return this._seq;
}

toPayload() {
return {
seq: this._seq,
};
}

static fromPayload(payload) {
return new WriteRequest(payload.seq);
}
}

class WriteRequestAck {
constructor(seq) {
this._type = EditorProtocolMessageType.WriteRequestAck;
this._seq = seq;
}

getType() {
return this._type;
}

getSeq() {
return this._seq;
}

toPayload() {
return {
seq: this._seq,
};
}

static fromPayload(payload) {
return new WriteRequestAck(payload.seq);
}
}

const serialize = message =>
JSON.stringify({
type: message.getType(),
payload: message.toPayload(),
version: 1,
});

const deserialize = (data) => {
try {
const { type, payload } = JSON.parse(data);

switch (type) {
case EditorProtocolMessageType.ConnectTo:
return ConnectTo.fromPayload(payload);

case EditorProtocolMessageType.NewPatches:
return NewPatches.fromPayload(payload);

case EditorProtocolMessageType.ApplyPatches:
return ApplyPatches.fromPayload(payload);

case EditorProtocolMessageType.WriteRequest:
return WriteRequest.fromPayload(payload);

case EditorProtocolMessageType.WriteRequestAck:
return WriteRequestAck.fromPayload(payload);

default:
throw new Error();
}
} catch (e) {
throw new Error('Deserialization error.');
}
};

export default {
ConnectTo,
NewPatches,
ApplyPatches,
WriteRequest,
WriteRequestAck,
serialize,
deserialize,
};
83 changes: 83 additions & 0 deletions plugins/atom/lib/tandem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use babel';

import { CompositeDisposable } from 'atom';
import TandemPlugin from './tandemPlugin';
import ConnectView from './connectView';

export default {

_tandemAgent: null,
_subscriptions: null,
_connectPanel: null,

activate() {
this._tandemAgent = new TandemPlugin();

const connectView = new ConnectView(
this._connectToHost.bind(this),
() => { this._inputPanel.hide(); },
);
this._inputPanel = atom.workspace.addModalPanel({
item: connectView,
visible: false,
});

this._subscriptions = new CompositeDisposable();
this._subscriptions.add(atom.commands.add('atom-workspace', {
'tandem:join-existing-session': () => this._connect(),
}));
this._subscriptions.add(atom.commands.add('atom-workspace', {
'tandem:start-session': () => this._start(),
}));
this._subscriptions.add(atom.commands.add('atom-workspace', {
'tandem:leave-session': () => this._stop(),
}));
},

deactivate() {
if (this._tandemAgent && this._tandemAgent.isActive()) {
this._tandemAgent.stop();
}
this._subscriptions.dispose();
},

_getTextEditor(newEditor) {
return new Promise((res) => {
if (newEditor) {
return res(atom.workspace.open());
}
const editor = atom.workspace.getActiveTextEditor();
if (editor) {
return res(editor);
}
// Open a new text editor if one is not open
return res(atom.workspace.open());
});
},

_start() {
this._getTextEditor().then((editor) => {
this._tandemAgent.start(editor.getBuffer());
});
},

_connect() {
this._inputPanel.show();
},

_connectToHost(input) {
this._inputPanel.hide();

const args = input.split(' ');
const ip = args[0];
const port = args[1];

this._getTextEditor(/* newEditor */ true).then((editor) => {
this._tandemAgent.start(editor.getBuffer(), ip, port);
});
},

_stop() {
this._tandemAgent.stop();
},
};