Skip to content

Instantly share code, notes, and snippets.

@reqshark
Created November 4, 2015 07:50
Show Gist options
  • Save reqshark/096e92dd4e851d187092 to your computer and use it in GitHub Desktop.
Save reqshark/096e92dd4e851d187092 to your computer and use it in GitHub Desktop.
atom server

my personal atom server

basic idea

hot reload makes development much more fun.

As I press ctrl-s on documents in atom editor i like being able to work with the file or send it off to servers and clients, or run native code compilation or other random linting, etc.

since atom is written in node.js, the idea is to open a var dgram = require('dgram') datagram socket for low latency distribution of any saved file path

All i care about is the file path

offload processing during text edits.

A separate processes is best suited for anything from reading the file to networking the buffer content and any other stuff that could bog down the app while i'm typing.

step 1

time to install atom editor.

install the shell commands if you haven't (from the main menu next to file and edit), so you can do stuff like $ atom . in your project directory

step 2

your editor config files are in the .atom directory of your home folder.

open your init script.

$ cd ~/.atom
$ open init.coffee

add this line to init.coffee, where it says editor.onDidSave ->:

# Your init script
#
# Atom will evaluate this file each time a new window is opened. It is run
# after packages are loaded/activated and after the previous editor state
# has been restored.
#
# An example hack to log to the console when each text editor is saved.
#
# atom.workspace.observeTextEditors (editor) ->
#   editor.onDidSave ->
#     console.log "Saved! #{editor.getPath()}"

atom.workspace.observeTextEditors (editor) ->
  editor.onDidSave ->
    require(__dirname + '/msgr.js')("#{editor.getPath()}")

we're passing the saved file path to a require call. it imports a function for handling the file path.

step 3

add the msgr.js script, since your editor will start calling it on-save.

$ touch ~/.atom/msgr.js
$ open ~/.atom/msgr.js

add something like this to msgr.js:

var dgram = require('dgram')
var sock  = dgram.createSocket('udp4')

module.exports = path

function path(p){
  var buf = Buffer(p)
  sock.send(buf, 0, buf.length, 42000, '127.0.0.1')
}

keep it on localhost or 127.0.0.1 and grab a high port like 42000 or whatever. now your editor can fire off little node buffers over udp's low maintenance, connectionless protocol

step 4

start a dgram server and handle the buffer from a separate node process! somewhere else, start your atomserver directory

$ mkdir myprojects && cd myprojects
$ mkdir atomserver && cd atomserver
$ touch package.json

in package.json add nanomsg, the ip module and osenv deps:

{
  "name":"atomserver by reqshark",
  "dependencies": {
    "ip":"",
    "nanomsg":"",
    "osenv":""
  }
}

install it with at least node 4 or 5

$ cd ~/myprojects/atomserver
$ npm install

step 5

add a udp buffer handler and a nanomsg socket for simple distributed networking. the atomserver's main.js:

var nano   = require('nanomsg')
var server = require('dgram').createSocket('udp4')
var pub    = nano.socket('pub', { tcpnodelay:true } )

// fill in your server's DNS address. if u prefer IP addresses, or soemthing on the local machine perhaps?
// the idea is to put the set of addresses wherever you're working on stuff
var addr1  = 'tcp://somepublicDNS.com:43000' || 'tcp://123.456.789.101.:43000'
var addr2 = 'tcp://10.0.1.67:43000'
// var addr3.. put as many as you want
// its better to use the same port across distributed addresses if possible, and we'll see why shortly

pub.connect(addr)
pub.connect(addr2) // nanomsg sockets can connect/bind to multiple endpoints

server.on('error', er); function er(err) { server.close(); throw err }
server.on('listening', heard)
function heard(){
  var addr = this.address()
  console.log('server listening '+ addr.address + ':' + addr.port)
}

server.on('message', handler)
function handler(buf){
  // this function will handle the buffer of our file path
  console.log('sending buf: '+buf);
  
  var msg = String(buf)
  
  // parse the path to find out what file was saved
  // if you're on windows, might want to use node's `require('path')` api
  var s = msg.split('/')

  //remove '','Users','reqshark'
  s.shift();s.shift();s.shift();

  //look at dir names in the home folder
  switch(s[0]){
    // get more specific than the s[0] case, like s[1], s[2], for `myprojects` etc.
    // add your custom case for your local file system project directory
    // resolve only if we're in the `reqshark directory` this time
    case 'reqshark'   : return r(s,require('path').resolve(msg)) 
    default           : console.log('unregistered path')
  }
}

// r() reads and formats our file data as a utf8 value assigned to the `d` or `data` property
// on an outbound packet published over nanomsg. its `p` property should store path info 
// needed for writing back at your set of remote server filesystems or subscriber sockets
function r (s,dir){
  require('fs').readFile(dir, 'utf8', function (er,dat) {
    pub.send( JSON.stringify({p:s.join('/'),d:dat}) )
  })
}

server.bind(46000,'127.0.0.1')

step 6

the subhost

$ touch ~/myprojects/atomserver/subhost.js
$ open ~/myprojects/atomserver/subhost.js

pubsub in nanomsg is isomorphic, so subscribers can bind to public addresses. Let's look at how our subhost does that in subhost.js, and we'll also make sure to handle infinite nested directories that we might have been working on in our editor:

// subhost.js

var fs        = require('fs');
var exec      = require('child_process').exec;

var nano      = require('nanomsg');
var sub       = nano.socket('sub', { tcpnodelay:true });
var addr      = 'tcp://' + require('ip').address() + ':43000';

console.log(addr) // let's make sure we have the correct address
// port consistency across endpoints makes it easier to reuse subhost.js

sub.on('data', function(msg){
  var m = JSON.parse(String(msg));
  
  // reset the `p` or path property for a remote machine users' home directory structure
  // `osenv` finds our way home on nearly every platform and operating system
  // although you may need to adjust the new `m.p`'s concatenated '/' forward slash
  m.p = require('osenv').home() + '/' + m.p;
  var dir = require('path').dirname(m.p);

  // need to forward the data onto other listening containers?
  // pub.send(JSON.stringify(m));

  //check if the remote path exists, write directories if necessary
  fs.access(dir, fs.R_OK | fs.W_OK, function(err) {
    if(!err) return writer(m);
    exec('mkdir -p ' + dir, function (err,e,o) { writer(m) });
  });
});

function writer (msg) {
  fs.writeFile(msg.p, msg.d, function (er){
    if(er)throw err; console.log('wrote:',msg.p);
  })
}

sub.bind( addr );

// if this is a proxy, make sure to bind any publisher sockets
// pub.bind( addr + ':49001' ); 

step 7

last step! after saving subhost, copy the atomsever directory structure using git or the scp command. Put it on a remote project directory that matches our local project structure.

we'll use it to confirm the accuracy and low latency text editor connection to subscribing remote environments that met our cases above in main.js

$ scp -r ~/myprojects/atomserver [email protected]:~/myprojects/atomserver
# login
$ ssh [email protected]

# check to make sure atomserver is there, and 
# make sure to remove your node_modules directory!
# since nanomsg is a native binding, chances are the operating system
# on ur remote machine uses a different binary

$ cd ~/myprojects/atomserver
$ rm -rf node_modules
$ npm install 

# test subhost.js and endpoint connections, make sure to run `main.js` locally in another tab

$ node subhost

# after confirming the protocol works i recommend using forever
# to keep the process going while logged out
$ npm install -g forvever
$ forever start subhost.js

contributing

feel free to improve everything here or run with it as your own project!

license

beerware

# Your init script
#
# Atom will evaluate this file each time a new window is opened. It is run
# after packages are loaded/activated and after the previous editor state
# has been restored.
#
# An example hack to log to the console when each text editor is saved.
#
# atom.workspace.observeTextEditors (editor) ->
# editor.onDidSave ->
# console.log "Saved! #{editor.getPath()}"
atom.workspace.observeTextEditors (editor) ->
editor.onDidSave ->
require(__dirname + '/msgr.js')("#{editor.getPath()}")
var nano = require('nanomsg')
var server = require('dgram').createSocket('udp4')
var pub = nano.socket('pub', { tcpnodelay:true } )
// fill in your server's DNS address. if u prefer IP addresses, or soemthing on the local machine perhaps?
// the idea is to put the set of addresses wherever you're working on stuff
var addr1 = 'tcp://somepublicDNS.com:43000' || 'tcp://123.456.789.101.:43000'
var addr2 = 'tcp://10.0.1.67:43000'
// var addr3.. put as many as you want
// its better to use the same port across distributed addresses if possible, and we'll see why shortly
pub.connect(addr)
pub.connect(addr2) // nanomsg sockets can connect/bind to multiple endpoints
server.on('error', er); function er(err) { server.close(); throw err }
server.on('listening', heard)
function heard(){
var addr = this.address()
console.log('server listening '+ addr.address + ':' + addr.port)
}
server.on('message', handler)
function handler(buf){
// this function will handle the buffer of our file path
console.log('sending buf: '+buf);
var msg = String(buf)
// parse the path to find out what file was saved
// if you're on windows, might want to use node's `require('path')` api
var s = msg.split('/')
//remove '','Users','reqshark'
s.shift();s.shift();s.shift();
//look at dir names in the home folder
switch(s[0]){
// get more specific than the s[0] case, like s[1], s[2], for `myprojects` etc.
// add your custom case for your local file system project directory
// resolve only if we're in the `reqshark directory` this time
case 'reqshark' : return r(s,require('path').resolve(msg))
default : console.log('unregistered path')
}
}
// r() reads and formats our file data as a utf8 value assigned to the `d` or `data` property
// on an outbound packet published over nanomsg. its `p` property should store path info
// needed for writing back at your set of remote server filesystems or subscriber sockets
function r (s,dir){
require('fs').readFile(dir, 'utf8', function (er,dat) {
pub.send( JSON.stringify({p:s.join('/'),d:dat}) )
})
}
server.bind(46000,'127.0.0.1')
var dgram = require('dgram')
var sock = dgram.createSocket('udp4')
module.exports = path
function path(p){
var buf = Buffer(p)
sock.send(buf, 0, buf.length, 42000, '127.0.0.1')
}
{
"name":"atomserver by reqshark",
"dependencies": {
"ip":"",
"nanomsg":"",
"osenv":""
}
}
// subhost.js
var fs = require('fs');
var exec = require('child_process').exec;
var nano = require('nanomsg');
var sub = nano.socket('sub', { tcpnodelay:true });
var addr = 'tcp://' + require('ip').address() + ':43000';
console.log(addr) // let's make sure we have the correct address
// port consistency across endpoints makes it easier to reuse subhost.js
sub.on('data', function(msg){
var m = JSON.parse(String(msg));
// reset the `p` or path property for a remote machine users' home directory structure
// `osenv` finds our way home on nearly every platform and operating system
// although you may need to adjust the new `m.p`'s concatenated '/' forward slash
m.p = require('osenv').home() + '/' + m.p;
var dir = require('path').dirname(m.p);
// need to forward the data onto other listening containers?
// pub.send(JSON.stringify(m));
//check if the remote path exists, write directories if necessary
fs.access(dir, fs.R_OK | fs.W_OK, function(err) {
if(!err) return writer(m);
exec('mkdir -p ' + dir, function (err,e,o) { writer(m) });
});
});
function writer (msg) {
fs.writeFile(msg.p, msg.d, function (er){
if(er)throw err; console.log('wrote:',msg.p);
})
}
sub.bind( addr );
// if this is a proxy, make sure to bind any publisher sockets
// pub.bind( addr + ':49001' );
@nadirB
Copy link

nadirB commented Apr 4, 2016

Hi I would like to make a protocol and make Atom and A MaxMsp Module communicate in the same way as in Sublime with this script : https://github.com/arshiacont/antescofo-sublime-package
Do you think we can hack this python script to make it happen in Atom ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment