Communication

Juno works by booting a Julia client from Atom. When Julia starts it connects to Atom over a TCP port, and from that point on Julia and Atom can each send messages to each other. Messages are JSON objects, with a type header to tell the receiver how the message should be handled.

The code handling low-level communication is kept in client.coffee and comm.jl. However, the details of those files aren't particularly important – you only need to understand the communication API, which we'll go over here.

Sending messages from Atom

Communication works by sending messages with an appropriate type on one side and registering handlers for that type on the other. The handler then takes some action and returns data to the original sender. For example, on the Atom side messages are sent in CoffeeScript as follows:

client.msg 'eval', '2+2'

On the Julia side, we need to set up a handler for this message, which happens as follows:

handle("eval") do code
  eval(parse(code))
end

This is a very simplified version of the eval handler that you can find in the Atom.jl source code. It simply evaluates whatever it's given and returns the result – in this case, 4.

Often we want to do something with that return result in Atom – in this case, we'd like to display the result. We don't need to change anything on the Julia side to accomplish this; we can just use the rpc function from JS:

client.rpc('eval', '2+2').then (result) =>
  console.log data

This call sends the eval message, pulls the result field out of the returned JSON, and displays the result, 4, in the console.

This approach is exactly how Atom gets evaluation results, autocompletion and more from Julia – so it's easy to find more examples spread throughout the julia-client and Atom.jl source code.

As a first project, try implementing an Atom command (see the Atom docs) which sends this message to Julia, as well as adding the Julia handler above to Atom.jl. (You'll want to use a type other than eval to avoid clashes with actual evaluation.)

Sending messages from Julia

Julia has a similar mechanism to talk to Atom via the function

Atom.@msg type(args...)

Handlers are defined on the Atom side as follows:

client.handle 'log', (args...) ->
  console.log args

It's also possible for Julia to wait for a response from Atom, using the rpc function.

client.handle 'echo', (data) ->
  data

(It's very easy to add this code to julia-client's activate function if you want to try this out.)

Calling the following from the REPL:

Atom.@rpc echo(Dict(:a=>1, :b=>2))

will return Dict("a"=>1, "b"=>2). The data was passed to Atom and simply returned as-is. Try changing the handler to modify the data before returning it.

This mechanism is how Julia commands like Atom.select() are implemented, and in general it makes it very simple for Julia to control the Atom frontend – see frontend.jl and frontend.coffee

Debugging and Learning

A good way to get a handle on this stuff is just to use console.log and @show, on the Atom and Julia sides respectively, to take a peek at what's going over the wire. For example, it's easy to change the above Julia handler to

handle("eval") do data
  @show data
  @show Dict(:result => eval(parse(data["code"])))
end

This will show you both the data being sent to Julia (in the example above, Dict("code"=>"2+2")) and the data being sent back to Atom (Dict(:result => 4)). Modifying say, the completions handler in a similar way will show you what completion data Julia sends back to Atom (there will probably be a lot, so try looking at specific keys, for example).

You don't need to reload Atom or restart the Julia client every time you make a change like this. If you open a file from the Atom.jl source code, you should see from the status bar that Juno knows you're working with the Atom module (try evaluating current_module() if you're not sure). Evaluating handlers from within the Atom module will show you what message types are currently defined. If you change a handler, just press C-Enter to update it in place; you should see the effect of your update immediately next time the handler is triggered. For example, if you modify the eval handler as follows:

handle("eval") do data
  println(data["code"]) # <- insert this line
  # ...

and update it, you should find that the next time you evaluate you see the contents of the current editor dumped into the console. Thus, most features or fixes you'd want to add to Juno can be made without a long edit – compile – run cycle.