├── .gitignore
├── docs
├── static
│ ├── main_modulename.png
│ └── scratch_modulename.png
├── README.md
├── communication.md
└── workflow.md
├── CHANGELOG.md
├── lib
├── frontend.coffee
├── ui
│ ├── selector.coffee
│ ├── notifications.coffee
│ └── console.coffee
├── completions.coffee
├── connection
│ ├── terminal.coffee
│ ├── tcp.coffee
│ ├── process.coffee
│ ├── spawnInterruptibleJulia.ps1
│ └── client.coffee
├── eval.coffee
├── utils.coffee
├── julia-client.coffee
└── modules.coffee
├── README.md
├── menus
└── julia-client.cson
├── styles
├── julia-client.less
└── spinner.css
├── LICENSE.md
├── package.json
├── manual
└── README.md
├── keymaps
└── julia-client.cson
└── CONTRIBUTING.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | npm-debug.log
3 | node_modules
4 |
--------------------------------------------------------------------------------
/docs/static/main_modulename.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dpsanders/atom-julia-client/master/docs/static/main_modulename.png
--------------------------------------------------------------------------------
/docs/static/scratch_modulename.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dpsanders/atom-julia-client/master/docs/static/scratch_modulename.png
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.2.0
2 | * Improved scrolling for inline results
3 | * Inter-file links in errors and `methods` output
4 | * Interrupts also working on Windows
5 |
6 | ## 0.1.0 - First Release
7 | * Every feature added
8 | * Every bug fixed
9 |
--------------------------------------------------------------------------------
/lib/frontend.coffee:
--------------------------------------------------------------------------------
1 | client = require './connection/client'
2 | selector = require './ui/selector'
3 |
4 | module.exports =
5 | activate: ->
6 | client.handle 'select', ({items}, resolve) =>
7 | selector.show items, (item) =>
8 | resolve item: item
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Julia Client
2 |
3 | Julia client provides interactive IDE-like features for the Atom editor. You can spin up
4 | a Julia process to run in the background, evaluate code inline, get live autocompletion
5 | results, work with the inline console, and more.
6 |
7 | For set up instructions, please see [the manual](manual/). This software is very much in
8 | alpha stage so expect to get your hands a little dirty.
9 |
10 | [Chat](https://gitter.im/JunoLab/Juno) for informal discussion.
11 |
--------------------------------------------------------------------------------
/menus/julia-client.cson:
--------------------------------------------------------------------------------
1 | # See https://atom.io/docs/latest/hacking-atom-package-word-count#menus for more details
2 | # 'context-menu':
3 | # 'atom-text-editor': [
4 | # {
5 | # 'label': 'Toggle julia-client'
6 | # 'command': 'julia-client:toggle'
7 | # }
8 | # ]
9 | # 'menu': [
10 | # {
11 | # 'label': 'Packages'
12 | # 'submenu': [
13 | # 'label': 'julia-client'
14 | # 'submenu': [
15 | # {
16 | # 'label': 'Toggle'
17 | # 'command': 'julia-client:toggle'
18 | # }
19 | # ]
20 | # ]
21 | # }
22 | # ]
23 |
--------------------------------------------------------------------------------
/lib/ui/selector.coffee:
--------------------------------------------------------------------------------
1 | {SelectListView} = require 'atom-space-pen-views'
2 |
3 | module.exports =
4 | show: (xs, f) ->
5 | @selector ?= new SelectListView
6 | @selector.setItems []
7 | @selector.storeFocusedElement()
8 | @selector.viewForItem = (item) =>
9 | "
#{item}"
10 |
11 | if xs.constructor == Promise
12 | @selector.setLoading "Loading..."
13 | xs.then (xs) =>
14 | @selector.setItems xs
15 | else
16 | @selector.setItems xs
17 |
18 | panel = atom.workspace.addModalPanel(item: @selector)
19 | @selector.focusFilterEditor()
20 |
21 | confirmed = false
22 | @selector.confirmed = (item) =>
23 | f(item)
24 | confirmed = true
25 | @selector.cancel()
26 | @selector.cancelled = =>
27 | panel.destroy()
28 | f() unless confirmed
29 |
--------------------------------------------------------------------------------
/styles/julia-client.less:
--------------------------------------------------------------------------------
1 | // The ui-variables file is provided by base themes provided by Atom.
2 | //
3 | // See https://github.com/atom/atom-dark-ui/blob/master/styles/ui-variables.less
4 | // for a full listing of what's available.
5 | @import "ui-variables";
6 |
7 | .julia-client {
8 | .fade {
9 | color: @text-color-subtle;
10 | }
11 | }
12 |
13 | .julia {
14 | table.methods {
15 | td {
16 | padding-left: 5px;
17 | }
18 | }
19 | &.error, .error {
20 | color: @text-color-error;
21 | .fade {
22 | color: fade(@text-color-error, 75%);
23 | }
24 | a {
25 | color: @text-color-error;
26 | }
27 | }
28 | }
29 |
30 | atom-text-editor::shadow {
31 | div.julia-client.loading {
32 | position: absolute;
33 | right: 10px;
34 | bottom: 10px;
35 | z-index: 1;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/ui/notifications.coffee:
--------------------------------------------------------------------------------
1 | remote = require('remote')
2 | client = require '../connection/client'
3 |
4 | module.exports =
5 | notes: []
6 | window: remote.getCurrentWindow()
7 |
8 | activate: ->
9 | document.addEventListener 'focusin', =>
10 | @clear()
11 | @msgHandlers()
12 |
13 | enabled: () -> atom.config.get('julia-client.notifications')
14 |
15 | show: (msg) ->
16 | return unless @enabled() and not document.hasFocus()
17 | n = new Notification "Julia Client",
18 | body: msg
19 | icon: @iconPath
20 | n.onclick = =>
21 | @window.focus()
22 | @notes.push(n)
23 |
24 | clear: ->
25 | for note in @notes
26 | note.close()
27 | @notes = []
28 |
29 | msgHandlers: ->
30 | client.handle 'error', (options) =>
31 | atom.notifications.addError options.msg, options
32 |
--------------------------------------------------------------------------------
/lib/completions.coffee:
--------------------------------------------------------------------------------
1 | client = require './connection/client'
2 | run = require './eval'
3 |
4 | module.exports =
5 | selector: '.source.julia'
6 | inclusionPriority: 0
7 | filterSuggestions: true
8 | excludeLowerPriority: false
9 |
10 | completionsData: (ed, pos) ->
11 | module: ed.juliaModule
12 | cursor: run.cursor pos
13 | code: ed.getText()
14 | path: ed.getPath()
15 |
16 | getCompletions: (ed, pos, f) ->
17 | client.msg 'completions', @completionsData(ed, pos), (data) ->
18 | f data
19 |
20 | toCompletion: (c) ->
21 | if c.constructor == String
22 | text: c
23 | else
24 | c
25 |
26 | getSuggestions: ({editor, bufferPosition}) ->
27 | return [] unless client.isConnected()
28 | new Promise (resolve) =>
29 | @getCompletions editor, bufferPosition, (completions) =>
30 | resolve completions?.map(@toCompletion) or []
31 |
--------------------------------------------------------------------------------
/lib/connection/terminal.coffee:
--------------------------------------------------------------------------------
1 | child_process = require 'child_process'
2 | client = require './client'
3 |
4 | module.exports =
5 |
6 | escpath: (p) -> '"' + p + '"'
7 | escape: (sh) -> sh.replace(/"/g, '\\"')
8 |
9 | exec: (sh) ->
10 | child_process.exec sh, (err, stdout, stderr) ->
11 | if err?
12 | console.log err
13 |
14 | term: (sh) ->
15 | switch process.platform
16 | when "darwin"
17 | @exec "osascript -e 'tell application \"Terminal\" to activate'"
18 | @exec "osascript -e 'tell application \"Terminal\" to do script \"#{@escape(sh)}\"'"
19 | else
20 | @exec "#{@terminal()} \"#{@escape(sh)}\""
21 |
22 | terminal: -> atom.config.get("julia-client.terminal")
23 |
24 | jlpath: () -> atom.config.get("julia-client.juliaPath")
25 | jlargs: () -> atom.config.get("julia-client.juliaArguments")
26 |
27 | repl: -> @term "#{@escpath @jlpath()} #{@jlargs()}"
28 |
29 | client: (port) ->
30 | client.booting()
31 | @term "#{@escpath @jlpath()} #{@jlargs()} -P \"import Atom; Atom.connect(#{port})\""
32 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Mike Innes
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/lib/connection/tcp.coffee:
--------------------------------------------------------------------------------
1 | net = require 'net'
2 | client = require './client'
3 |
4 | module.exports =
5 | server: null
6 | port: null
7 | sock: null
8 |
9 | buffer: (f) ->
10 | buffer = ['']
11 | (data) ->
12 | str = data.toString()
13 | lines = str.split '\n'
14 | buffer[0] += lines.shift()
15 | buffer.push lines...
16 | while buffer.length > 1
17 | f buffer.shift()
18 |
19 | listen: (f) ->
20 | return f?(@port) if @port?
21 | client.isConnected = => @sock?
22 | client.output = (data) => @sock.write JSON.stringify data
23 | @server = net.createServer (c) =>
24 | if @sock then return c.end()
25 | @sock = c
26 | client.connected()
27 | c.on 'end', =>
28 | @sock = null
29 | client.disconnected()
30 | c.on 'error', (e) =>
31 | console.error 'Julia Client: TCP connection error:'
32 | console.error e
33 | @sock = null
34 | client.disconnected()
35 | c.on 'data', @buffer (s) =>
36 | client.input JSON.parse s
37 |
38 | @server.listen 0, =>
39 | @port = @server.address().port
40 | f?(@port)
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "julia-client",
3 | "main": "./lib/julia-client",
4 | "version": "0.1.2",
5 | "description": "Julia Evaluation",
6 | "keywords": [],
7 | "activationCommands": {
8 | "atom-text-editor": [
9 | "julia-client:evaluate",
10 | "julia-client:evaluate-all"
11 | ],
12 | "atom-workspace": [
13 | "julia-client:open-a-repl",
14 | "julia-client:start-repl-client",
15 | "julia-client:start-julia",
16 | "julia-client:toggle-console",
17 | "julia:open-startup-file",
18 | "julia:open-julia-home",
19 | "julia:open-package-in-new-window"
20 | ]
21 | },
22 | "repository": "https://github.com/JunoLab/atom-julia-client",
23 | "license": "MIT",
24 | "engines": {
25 | "atom": ">=1.0.0 <2.0.0"
26 | },
27 | "dependencies": {
28 | "atom-space-pen-views": "^2.0.0"
29 | },
30 | "consumedServices": {
31 | "status-bar": {
32 | "versions": {
33 | "^1.0.0": "consumeStatusBar"
34 | }
35 | },
36 | "ink": {
37 | "versions": {
38 | "*": "consumeInk"
39 | }
40 | }
41 | },
42 | "providedServices": {
43 | "autocomplete.provider": {
44 | "versions": {
45 | "2.0.0": "completions"
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/ui/console.coffee:
--------------------------------------------------------------------------------
1 | # TODO: modules, history
2 |
3 | client = require '../connection/client'
4 | notifications = require '../ui/notifications'
5 |
6 | module.exports =
7 | activate: ->
8 | @create()
9 |
10 | @cmd = atom.commands.add 'atom-workspace',
11 | "julia-client:clear-console": =>
12 | @c.reset()
13 |
14 | client.handle 'info', ({msg}) =>
15 | @c.info msg
16 |
17 | client.handle 'result', ({result}) =>
18 | view = if result.type then result.view else result
19 | view = @ink.tree.fromJson view
20 | @ink.links.linkify view[0]
21 | error = result.type == 'error'
22 | @ink.tree.toggle view unless error
23 | @c.result view,
24 | error: error
25 |
26 | deactivate: ->
27 | @cmd.dispose()
28 |
29 | create: ->
30 | @c = new @ink.Console
31 | @c.setGrammar atom.grammars.grammarForScopeName('source.julia')
32 | @c.view[0].classList.add 'julia'
33 | @c.view.getTitle = -> "Julia"
34 | @c.modes = => @replModes
35 | @c.onEval (ed) => @eval ed
36 | @c.input()
37 | @loading.onWorking => @c.view.loading true
38 | @loading.onDone => @c.view.loading false
39 |
40 | toggle: -> @c.toggle()
41 |
42 | eval: (ed) ->
43 | if ed.getText()
44 | client.start()
45 | @c.done()
46 | client.msg 'eval-repl', {code: ed.getText(), mode: ed.inkConsoleMode?.name}, (result) =>
47 | @c.input()
48 | notifications.show "Evaluation Finished"
49 |
50 | replModes:
51 | ';':
52 | name: 'shell'
53 | icon: 'terminal'
54 | grammar: atom.grammars.grammarForScopeName('source.shell')
55 | '?':
56 | name: 'help'
57 | icon: 'question'
58 |
--------------------------------------------------------------------------------
/manual/README.md:
--------------------------------------------------------------------------------
1 | # Julia Client Manual
2 |
3 | This is the user manual for the atom-julia-client plugin and its dependencies. It's a little
4 | sparse at the moment – reflecting the fact that the plugin isn't ready for all users – but
5 | basic setup is included. If you'd like to get involved you can check out the [dev
6 | setup](../docs) instead.
7 |
8 | ## Installation
9 |
10 | In order to get Julia-Client working you need to install its dependencies in both Julia and
11 | Atom. To start with, you'll need to work with Julia v0.4, if you're not already. You can
12 | [build Julia from source](https://github.com/JuliaLang/julia) or [download the nightly
13 | build](http://julialang.org/downloads/) – the download is easier to get started with,
14 | particularly on Windows. From Julia, install the `Atom` package. You currently also need to
15 | run `Pkg.checkout("JuliaParser")` in order to avoid errors from the latest release.
16 |
17 | Next, go to the Atom settings pane and install the packages `language-julia`, `ink` and
18 | `julia-client`. If you open the command palette and type `Julia` you should see that
19 | Julia-related commands are now available. The last step is to make sure Atom can find Julia –
20 | if the `julia` command is not on your path, you need to go into the julia-client settings
21 | and set the path to the Julia binary, which is `[wherever you installed
22 | Julia]/bin/julia` (or `[same]\bin\julia.exe` on Windows).
23 |
24 | Finally, run the `Julia Client: Toggle Console` command from Atom. When the console opens,
25 | type a Julia expression (e.g. `2+2`) and press `Enter` to evaluate it. This may take a while
26 | the first time as precompilation runs, but booting Julia next time around will be much
27 | quicker.
28 |
--------------------------------------------------------------------------------
/keymaps/julia-client.cson:
--------------------------------------------------------------------------------
1 | # TODO: don't apply all these commands to the console
2 |
3 | '.platform-darwin .item-views > atom-text-editor[data-grammar="source julia"]:not([mini])':
4 | 'cmd-enter': 'julia-client:evaluate'
5 | 'cmd-shift-enter': 'julia-client:evaluate-all'
6 | 'cmd-j cmd-m': 'julia-client:set-working-module'
7 | 'cmd-shift-j cmd-shift-m': 'julia-client:reset-working-module'
8 | 'ctrl-`': 'julia-client:toggle-console'
9 |
10 | '.platform-darwin .console':
11 | 'ctrl-c': 'julia-client:interrupt-julia'
12 | 'ctrl-`': 'julia-client:toggle-console'
13 |
14 | '.platform-darwin atom-workspace':
15 | 'cmd-j cmd-r': 'julia-client:open-a-repl'
16 | 'cmd-shift-j cmd-shift-r': 'julia-client:start-repl-client'
17 | 'cmd-j cmd-s': 'julia-client:start-julia'
18 | 'cmd-j cmd-c': 'julia-client:clear-console'
19 | 'cmd-j cmd-l': 'julia-client:reset-loading-indicator'
20 | 'cmd-j cmd-k': 'julia-client:kill-julia'
21 |
22 | '.platform-win32 .item-views > atom-text-editor[data-grammar="source julia"]:not([mini]),
23 | .platform-linux .item-views > atom-text-editor[data-grammar="source julia"]:not([mini])':
24 | 'ctrl-enter': 'julia-client:evaluate'
25 | 'ctrl-shift-enter': 'julia-client:evaluate-all'
26 | 'ctrl-j ctrl-m': 'julia-client:set-working-module'
27 | 'ctrl-alt-j ctrl-alt-m': 'julia-client:reset-working-module'
28 | 'ctrl-`': 'julia-client:toggle-console'
29 |
30 | '.platform-win32 .console,
31 | .platform-linux .console':
32 | 'ctrl-shift-c': 'julia-client:interrupt-julia'
33 | 'ctrl-`': 'julia-client:toggle-console'
34 |
35 | '.platform-win32 atom-workspace,
36 | .platform-linux atom-workspace':
37 | 'ctrl-j ctrl-r': 'julia-client:open-a-repl'
38 | 'ctrl-alt-j ctrl-alt-r': 'julia-client:start-repl-client'
39 | 'ctrl-j ctrl-s': 'julia-client:start-julia'
40 | 'ctrl-j ctrl-c': 'julia-client:clear-console'
41 | 'ctrl-j ctrl-c': 'julia-client:reset-loading-indicator'
42 | 'ctrl-j ctrl-k': 'julia-client:kill-julia'
43 |
--------------------------------------------------------------------------------
/lib/connection/process.coffee:
--------------------------------------------------------------------------------
1 | process = require 'child_process'
2 | client = require './client'
3 | net = require 'net'
4 |
5 | module.exports =
6 | jlpath: () -> atom.config.get("julia-client.juliaPath")
7 | # TODO: this is very naïve.
8 | jlargs: () -> atom.config.get("julia-client.juliaArguments").split ' '
9 |
10 | start: (port, cons) ->
11 | return if @proc?
12 | client.booting()
13 | @spawnJulia(port)
14 | @onStart()
15 | @proc.on 'exit', (code, signal) =>
16 | cons.c.err "Julia has stopped: #{code}, #{signal}"
17 | cons.c.input() unless cons.c.isInput
18 | @onStop()
19 | @proc = null
20 | client.cancelBoot()
21 | @proc.stdout.on 'data', (data) =>
22 | text = data.toString()
23 | if text then cons.c.out text
24 | @proc.stderr.on 'data', (data) =>
25 | text = data.toString()
26 | if text then cons.c.err text
27 |
28 | onStart: ->
29 | @cmds = atom.commands.add 'atom-workspace',
30 | 'julia-client:kill-julia': => @killJulia()
31 | 'julia-client:interrupt-julia': => @interruptJulia()
32 |
33 | onStop: ->
34 | @cmds?.dispose()
35 |
36 | spawnJulia: (port) ->
37 | switch process.platform
38 | when 'win32'
39 | @proc = process.spawn("powershell", ["-ExecutionPolicy", "bypass", "& \"#{__dirname}\\spawnInterruptibleJulia.ps1\" -port #{port} -jlpath \"#{@jlpath()}\" -jloptions \"#{@jlargs().join(' ')}\""])
40 | else
41 | @proc = process.spawn(@jlpath(), [@jlargs()..., '-e', "import Atom; @sync Atom.connect(#{port})"])
42 |
43 | interruptJulia: ->
44 | switch process.platform
45 | when 'win32'
46 | @sendSignalToWrapper('SIGINT')
47 | else
48 | @proc.kill('SIGINT')
49 |
50 | killJulia: ->
51 | switch process.platform
52 | when 'win32'
53 | @proc.kill()
54 | else
55 | @sendSignalToWrapper('KILL')
56 |
57 | sendSignalToWrapper: (signal) ->
58 | wrapper = net.connect(port: 26992)
59 | wrapper.setNoDelay()
60 | wrapper.write(signal)
61 | wrapper.end()
62 |
--------------------------------------------------------------------------------
/lib/connection/spawnInterruptibleJulia.ps1:
--------------------------------------------------------------------------------
1 | param(
2 | [Int32] $port,
3 | [string] $jlpath,
4 | [string] $jloptions
5 | )
6 |
7 | # start Julia
8 | $proc = Start-Process $jlpath "$jloptions -e `"import Atom; @sync Atom.connect($port)`"" -NoNewWindow -PassThru
9 |
10 | # import GenerateConsoleCtrlEvent:
11 | $MethodDefinition = @'
12 | [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
13 | public static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, uint dwProcessGroupId);
14 | '@
15 |
16 | $Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -Namespace 'Win32' -PassThru
17 |
18 | function Receive-TCPMessage {
19 | param ( [ValidateNotNullOrEmpty()]
20 | [int] $Port
21 | )
22 | try {
23 | $endpoint = new-object System.Net.IPEndPoint([ipaddress]::any, $port)
24 | $listener = new-object System.Net.Sockets.TcpListener $endpoint
25 | $listener.start()
26 |
27 | $data = $listener.AcceptTcpClient() # will block here until connection
28 | $bytes = New-Object System.Byte[] 6
29 | $stream = $data.GetStream()
30 |
31 | while (($i = $stream.Read($bytes,0,$bytes.Length)) -ne 0){
32 | $EncodedText = New-Object System.Text.ASCIIEncoding
33 | $data = $EncodedText.GetString($bytes,0, $i)
34 | Write-Output $data
35 | }
36 |
37 | $stream.close()
38 | $listener.stop()
39 | }
40 | catch [exception]{
41 | echo "julia-client: Internal Error:"
42 | echo "$exception"
43 | }
44 | }
45 |
46 | # the port should probably be determined dynamically (by nodejs):
47 | while ($true){
48 | $msg = Receive-TCPMessage -Port 26992 # wait for interrupts
49 | if ($msg -match "SIGINT"){
50 | $status = $Kernel32::GenerateConsoleCtrlEvent(0, $proc.Id)
51 | # this is necessary for GenerateConsoleCtrlEvent to actually do something:
52 | echo "Interrupting Julia..."
53 | if (!$status) {
54 | echo "julia-client: Internal Error: Interrupting Julia failed."
55 | }
56 | }
57 | if ($msg -match "KILL"){
58 | $proc.Kill()
59 | Exit
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/eval.coffee:
--------------------------------------------------------------------------------
1 | client = require './connection/client'
2 | notifications = require './ui/notifications'
3 |
4 | module.exports =
5 | cursor: ({row, column}) ->
6 | row: row+1
7 | column: column+1
8 |
9 | evalData: (editor, selection) ->
10 | start = selection.getHeadBufferPosition()
11 | end = selection.getTailBufferPosition()
12 | if not start.isLessThan end then [start, end] = [end, start]
13 |
14 | code: editor.getText()
15 | module: editor.juliaModule
16 | path: editor.getPath() || 'untitled-' + editor.getBuffer().inkId
17 | start: @cursor start
18 | end: @cursor end
19 |
20 | # TODO: get block bounds as a seperate step
21 | # TODO: implement block finding in Atom
22 | eval: ->
23 | editor = atom.workspace.getActiveTextEditor()
24 | for sel in editor.getSelections()
25 | client.msg 'eval', @evalData(editor, sel), ({start, end, result}) =>
26 | view = if result.type then result.view else result
27 | view = @ink.tree.fromJson(view)[0]
28 | @ink.links.linkify view
29 | r = @ink?.results.showForLines editor, start-1, end-1,
30 | content: view
31 | error: result.type == 'error'
32 | clas: 'julia'
33 | if result.type == 'error' and result.highlights
34 | @showError r, result.highlights
35 | notifications.show "Evaluation Finished"
36 |
37 | evalAll: ->
38 | editor = atom.workspace.getActiveTextEditor()
39 | client.msg 'eval-all', {
40 | path: editor.getPath()
41 | module: editor.juliaModule
42 | code: editor.getText()
43 | },
44 | (result) =>
45 | notifications.show "Evaluation Finished"
46 |
47 | showError: (r, lines) ->
48 | @errorLines?.lights.destroy()
49 | lights = @ink.highlights.errorLines (file: file, line: line-1 for {file, line} in lines)
50 | @errorLines = {r, lights}
51 |
52 | destroyResult = r.destroy.bind r
53 | r.destroy = =>
54 | if @errorLines?.r == r
55 | @errorLines.lights.destroy()
56 | destroyResult()
57 |
--------------------------------------------------------------------------------
/lib/utils.coffee:
--------------------------------------------------------------------------------
1 | path = require 'path'
2 | shell = require 'shell'
3 | fs = require 'fs'
4 | client = require './connection/client'
5 | selector = require './ui/selector'
6 |
7 | module.exports =
8 | homedir: ->
9 | if process.platform == 'win32'
10 | process.env.USERPROFILE
11 | else
12 | process.env.HOME
13 |
14 | home: (p...) -> path.join @homedir(), p...
15 |
16 | juliaHome: -> process.env.JULIA_HOME or @home '.julia'
17 |
18 | pkgDir: (f) ->
19 | fs.readdir @juliaHome(), (err, data) =>
20 | dir = data?.filter((path)=>path.startsWith('v')).sort().pop()
21 | dir? and f path.join(@juliaHome(), dir)
22 |
23 | packages: (f) ->
24 | @pkgDir (dir) =>
25 | fs.readdir dir, (err, data) =>
26 | ps = data?.filter((path)=>!path.startsWith('.') and
27 | ["METADATA","REQUIRE","META_BRANCH"].indexOf(path) < 0)
28 | ps? and f(ps, dir)
29 |
30 | openPackage: ->
31 | dir = ''
32 | ps = new Promise (resolve) =>
33 | @packages (ps, d) =>
34 | dir = d
35 | resolve ps
36 | selector.show ps, (pkg) =>
37 | return unless pkg?
38 | atom.open pathsToOpen: [path.join dir, pkg]
39 |
40 | cdHere: ->
41 | file = atom.workspace.getActiveTextEditor()?.getPath()
42 | file? or atom.notifications.addError 'This file has no path.'
43 | client.msg 'cd', {path: path.dirname(file)}
44 |
45 | cdProject: ->
46 | dirs = atom.project.getPaths()
47 | if dirs.length < 1
48 | atom.notifications.addError 'This project has no folders.'
49 | else if dirs.length == 1
50 | client.msg 'cd', {path: dirs[0]}
51 | else
52 | selector.show dirs, (dir) =>
53 | return unless dir?
54 | client.msg 'cd', {path: dir}
55 |
56 | cdHome: ->
57 | client.msg 'cd', {path: @home()}
58 |
59 | commands: (subs) ->
60 | subs.add atom.commands.add 'atom-workspace',
61 | 'julia:open-startup-file': => atom.workspace.open @home '.juliarc.jl'
62 | 'julia:open-julia-home': => shell.openItem @juliaHome()
63 | 'julia:open-package-in-new-window': => @openPackage()
64 |
65 | subs.add atom.commands.add 'atom-text-editor',
66 | 'julia-client:work-in-file-folder': =>
67 | client.require => @cdHere()
68 | 'julia-client:work-in-project-folder': =>
69 | client.require => @cdProject()
70 | 'julia-client:work-in-home-folder': =>
71 | client.require => @cdHome()
72 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Atom-Julia
2 |
3 | Julia support in Atom is in a very, very early state – we love to have people kicking the
4 | tires, but be ready to get your hands dirty!
5 |
6 | ## Help & Bug Reporting
7 |
8 | This project is composed of many sub-projects, and it can be hard to know the appropriate
9 | place to file issues. For that reason we prefer that non-developers report issues on the
10 | [Juno discussion forum](http://discuss.junolab.org/) under the "issue" category, and we can
11 | funnel things through to the right places from there.
12 |
13 | ## Contributing
14 |
15 | If you have feature ideas you'd like to implement, or bugs you'd like to fix, feel free to
16 | open a [discussion](http://discuss.junolab.org/) under the "dev" category – we're always happy
17 | to help people flesh out their ideas or get unstuck on problems.
18 |
19 | If you look over the GitHub issues for the various packages, you may notice some labelled
20 | [up for
21 | grabs](https://github.com/JunoLab/atom-julia-client/issues?q=is%3Aopen+is%3Aissue+label%3A%22up+for+grabs%22).
22 | These are features or bugs for which the implementation or fix is reasonably straightforward –
23 | they might take a few hours of effort or more, but they won't involve enormous expert-level
24 | challenges. As above, feel free to open up a discussion on these and we'll help you get
25 | going.
26 |
27 | Please read the [developer set up guidelines](docs/) to get started. There's a basic
28 | development tutorial there, but as you get deeper you'll want some more general resources:
29 |
30 | * [Julia Documentation](http://docs.julialang.org/en/latest/) – for learning about the Julia
31 | language.
32 | * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript) – by far the best reference
33 | on the JavaScript language and browser window APIs.
34 | * [CoffeeScript](http://coffeescript.org/) – Atom and its packages use this to make working
35 | with JS a little more convenient.
36 | * [The Atom Docs](https://atom.io/docs) – the Atom Flight Manual is a very readable introduction
37 | to Atom's internals, and the API docs are a useful reference.
38 | * [julia-client developer docs](docs/) – These docs go into more detail about the internals
39 | of this project and the development workflow.
40 |
41 | Most open source projects, including ours, use [git](http://git-scm.org) to help work
42 | together. There are plenty of git tutorials around, and the various GUI clients (e.g. GitHub
43 | for Windows/Mac, SourceTree) are very helpful for learning the ropes.
44 |
--------------------------------------------------------------------------------
/lib/connection/client.coffee:
--------------------------------------------------------------------------------
1 | {Emitter} = require 'atom'
2 |
3 | module.exports =
4 |
5 | # Messaging
6 |
7 | handlers: {}
8 | callbacks: {}
9 | queue: []
10 | id: 0
11 |
12 | input: ([type, data]) ->
13 | if @handlers.hasOwnProperty type
14 | if data.callback?
15 | @handlers[type] data, (result) =>
16 | @msg data.callback, result
17 | else
18 | @handlers[type] data
19 | else if @callbacks.hasOwnProperty type
20 | try
21 | @callbacks[type] data
22 | finally
23 | delete @callbacks[type]
24 | @loading.done()
25 | else
26 | console.log "julia-client: unrecognised message #{type}"
27 | console.log data
28 |
29 | output: (data) ->
30 |
31 | msg: (type, data, f) ->
32 | if f?
33 | data.callback = @id = @id+1
34 | @callbacks[@id] = f
35 | @loading.working()
36 | if @isConnected()
37 | @output [type, data]
38 | else
39 | @queue.push [type, data]
40 |
41 | handle: (type, f) ->
42 | @handlers[type] = f
43 |
44 | # Connecting & Booting
45 |
46 | emitter: new Emitter()
47 |
48 | onConnected: (cb) -> @emitter.on('connected', cb)
49 | onDisconnected: (cb) -> @emitter.on('disconnected', cb)
50 |
51 | isConnected: -> false
52 |
53 | connected: ->
54 | @emitter.emit 'connected'
55 | if @isBooting
56 | @isBooting = false
57 | @loading.done()
58 | @output msg for msg in @queue
59 | @queue = []
60 |
61 | disconnected: ->
62 | @emitter.emit 'disconnected'
63 | @reset()
64 |
65 | booting: ->
66 | @isBooting = true
67 | @loading.working()
68 |
69 | cancelBoot: ->
70 | if @isBooting
71 | @isBooting = false
72 | @reset()
73 |
74 | reset: ->
75 | @cancelBoot()
76 | @loading.reset()
77 | @queue = []
78 | @callbacks = {}
79 |
80 | # Management & UI
81 |
82 | connectedError: ->
83 | if @isConnected()
84 | atom.notifications.addError "Can't create a new client.",
85 | detail: "There is already a Julia client running."
86 | true
87 | else
88 | false
89 |
90 | notConnectedError: ->
91 | if not @isConnected()
92 | atom.notifications.addError "Can't do that without a Julia client.",
93 | detail: "Try connecting a client by evaluating something."
94 | true
95 | else
96 | false
97 |
98 | require: (f) -> @notConnectedError() or f()
99 | disrequire: (f) -> @connectedError() or f()
100 |
101 | start: ->
102 | if not @isConnected() and not @isBooting
103 | atom.commands.dispatch atom.views.getView(atom.workspace),
104 | 'julia-client:start-julia'
105 |
--------------------------------------------------------------------------------
/styles/spinner.css:
--------------------------------------------------------------------------------
1 | /* Thanks to https://github.com/tobiasahlin/SpinKit/ */
2 |
3 | /*
4 | * Usage:
5 | *
6 | *
7 | *
8 | *
9 | *
10 | *
11 | *
12 | *
13 | */
14 | .sk-folding-cube {
15 | margin: 0;
16 | width: 20px;
17 | height: 20px;
18 | position: relative;
19 | -webkit-transform: rotateZ(45deg);
20 | transform: rotateZ(45deg); }
21 | .sk-folding-cube .sk-cube {
22 | float: left;
23 | width: 50%;
24 | height: 50%;
25 | position: relative;
26 | -webkit-transform: scale(1.1);
27 | -ms-transform: scale(1.1);
28 | transform: scale(1.1); }
29 | .sk-folding-cube .sk-cube:before {
30 | content: '';
31 | position: absolute;
32 | top: 0;
33 | left: 0;
34 | width: 100%;
35 | height: 100%;
36 | background-color: #fff;
37 | -webkit-animation: sk-foldCubeAngle 2.4s infinite linear both;
38 | animation: sk-foldCubeAngle 2.4s infinite linear both;
39 | -webkit-transform-origin: 100% 100%;
40 | -ms-transform-origin: 100% 100%;
41 | transform-origin: 100% 100%; }
42 | .sk-folding-cube .sk-cube2 {
43 | -webkit-transform: scale(1.1) rotateZ(90deg);
44 | transform: scale(1.1) rotateZ(90deg); }
45 | .sk-folding-cube .sk-cube3 {
46 | -webkit-transform: scale(1.1) rotateZ(180deg);
47 | transform: scale(1.1) rotateZ(180deg); }
48 | .sk-folding-cube .sk-cube4 {
49 | -webkit-transform: scale(1.1) rotateZ(270deg);
50 | transform: scale(1.1) rotateZ(270deg); }
51 | .sk-folding-cube .sk-cube2:before {
52 | -webkit-animation-delay: 0.3s;
53 | animation-delay: 0.3s; }
54 | .sk-folding-cube .sk-cube3:before {
55 | -webkit-animation-delay: 0.6s;
56 | animation-delay: 0.6s; }
57 | .sk-folding-cube .sk-cube4:before {
58 | -webkit-animation-delay: 0.9s;
59 | animation-delay: 0.9s; }
60 |
61 | @-webkit-keyframes sk-foldCubeAngle {
62 | 0%, 10% {
63 | -webkit-transform: perspective(140px) rotateX(-180deg);
64 | transform: perspective(140px) rotateX(-180deg);
65 | opacity: 0; }
66 | 25%, 75% {
67 | -webkit-transform: perspective(140px) rotateX(0deg);
68 | transform: perspective(140px) rotateX(0deg);
69 | opacity: 1; }
70 | 90%, 100% {
71 | -webkit-transform: perspective(140px) rotateY(180deg);
72 | transform: perspective(140px) rotateY(180deg);
73 | opacity: 0; } }
74 |
75 | @keyframes sk-foldCubeAngle {
76 | 0%, 10% {
77 | -webkit-transform: perspective(140px) rotateX(-180deg);
78 | transform: perspective(140px) rotateX(-180deg);
79 | opacity: 0; }
80 | 25%, 75% {
81 | -webkit-transform: perspective(140px) rotateX(0deg);
82 | transform: perspective(140px) rotateX(0deg);
83 | opacity: 1; }
84 | 90%, 100% {
85 | -webkit-transform: perspective(140px) rotateY(180deg);
86 | transform: perspective(140px) rotateY(180deg);
87 | opacity: 0; } }
88 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Contents
2 |
3 | * Basics
4 | * [Developer Install](#developer-install) – get going with Atom and Julia package
5 | development.
6 | * [Communication](communication.md) – Details how Atom and Julia both communicate with
7 | each other.
8 |
9 | These docs are evolving as we figure out the best way to get people involved. If it's hard
10 | to get things working, or anything seems out of date, broken, or just plain confusing,
11 | please do let us know so we can fix it. Even better, file a PR!
12 |
13 | # Developer Install
14 |
15 | Firstly, you need to be working in Julia v0.4 for Atom. Currently, you can either build Julia
16 | from source or download a [nightly build](http://julialang.org/downloads/).
17 |
18 | Julia support in Atom consists of a number of packages for both Julia and Atom:
19 |
20 | * [language-julia](https://github.com/JuliaLang/atom-language-julia) – Provides basic
21 | language support for Atom, e.g. syntax highlighting.
22 | * [ink](https://github.com/JunoLab/atom-ink) – Provides generic UI components for building
23 | IDEs in Atom (e.g. the console lives here).
24 | * [CodeTools.jl](http://github.com/JunoLab/CodeTools.jl) – provides backend editor support
25 | for Julia, e.g. autocompletion and evaluation.
26 | * [Atom.jl](http://github.com/JunoLab/Atom.jl) and
27 | [julia-client](http://github.com/JunoLab/atom-julia-client) – these packages tie everything
28 | together. julia-client boots Julia from inside Atom, then communicates with the Atom.jl
29 | package to provide e.g. autocompletion and evaluation within the editor.
30 |
31 | You can install *language-julia* by using Atom's `Install Packages And Themes` command and
32 | searching for it. The Julia packages, *Atom.jl* and *CodeTools.jl*, can be installed via
33 |
34 | ```julia
35 | Pkg.clone("http://github.com/JunoLab/Atom.jl")
36 | Pkg.clone("http://github.com/JunoLab/CodeTools.jl")
37 | ```
38 |
39 | If you already have these packages change `clone` to `checkout` here.
40 |
41 | To install the Atom packages, start by uninstalling them completely if you have them
42 | already, then run the following commands in a folder of your choice (it's conventional,
43 | though not required, to do this in `~/github`):
44 |
45 | ```shell
46 | git clone http://github.com/JunoLab/atom-ink ink
47 | cd ink
48 | apm install
49 | apm link .
50 | cd ..
51 |
52 | git clone http://github.com/JunoLab/atom-julia-client julia-client
53 | cd julia-client
54 | apm install
55 | apm link .
56 | cd ..
57 | ```
58 |
59 | It's a good idea to keep these up to date by running `Pkg.update()` in Julia and syncing the
60 | package repos every now and then.
61 |
62 | Atom will need to be reloaded, either by closing and reopening it or by running the `Window:
63 | Reload` command. At this point, you should find that there are a bunch of new Julia commands
64 | available in Atom – type "Julia" into the command palette to see what's available. If the
65 | `julia` command isn't on your path already, set the Julia path in the julia-client settings
66 | panel.
67 |
68 | Get started by going into a buffer set to Julia syntax, typing `2+2`, and pressing
69 | `C-Enter` (where `C` stands for `Ctrl`, or `Cmd` on OS X). After the client boots you
70 | should see the result pop up next to the text. You can also work in the Atom REPL by pressing
71 | `` Ctrl-` `` – just type in the input box and `Shift-Enter` to evaluate.
72 |
73 | On OS X you may an error because the Julia command cannot be found, even though it's on your
74 | path. To solve this you can either set the full path in the package settings, or start Atom
75 | from the terminal with the `atom` command.
76 |
--------------------------------------------------------------------------------
/lib/julia-client.coffee:
--------------------------------------------------------------------------------
1 | {CompositeDisposable} = require 'atom'
2 | http = require 'http'
3 | terminal = require './connection/terminal'
4 | client = require './connection/client'
5 | process = require './connection/process'
6 | tcp = require './connection/tcp'
7 | modules = require './modules'
8 | evaluation = require './eval'
9 | notifications = require './ui/notifications'
10 | utils = require './utils'
11 | completions = require './completions'
12 | frontend = require './frontend'
13 | cons = require './ui/console'
14 |
15 | defaultTerminal =
16 | switch process.platform
17 | when 'darwin'
18 | 'Terminal.app'
19 | when 'win32'
20 | 'cmd /C start cmd /C'
21 | else
22 | 'x-terminal-emulator -e'
23 |
24 | module.exports = JuliaClient =
25 | config:
26 | juliaPath:
27 | type: 'string'
28 | default: 'julia'
29 | description: 'The location of the Julia binary'
30 | juliaArguments:
31 | type: 'string'
32 | default: '-q'
33 | description: 'Command-line arguments to pass to Julia'
34 | notifications:
35 | type: 'boolean'
36 | default: true
37 | description: 'Enable notifications for evaluation'
38 | terminal:
39 | type: 'string'
40 | default: defaultTerminal
41 | description: 'Command used to open a terminal. (Windows/Linux only)'
42 |
43 | activate: (state) ->
44 | @subscriptions = new CompositeDisposable
45 | @commands @subscriptions
46 | modules.activate()
47 | notifications.activate()
48 | frontend.activate()
49 | client.onConnected =>
50 | notifications.show("Client Connected")
51 | @withInk =>
52 | cons.activate()
53 |
54 | try
55 | if id = localStorage.getItem 'metrics.userId'
56 | http.get "http://mikeinn.es/hit?id=#{id}&app=atom-julia"
57 |
58 | deactivate: ->
59 | @subscriptions.dispose()
60 | modules.deactivate()
61 | cons.deactivate()
62 | @spinner.dispose()
63 |
64 | commands: (subs) ->
65 | subs.add atom.commands.add 'atom-text-editor',
66 | 'julia-client:evaluate': (event) =>
67 | @withInk =>
68 | client.start()
69 | evaluation.eval()
70 | 'julia-client:evaluate-all': (event) =>
71 | @withInk =>
72 | client.start()
73 | evaluation.evalAll()
74 |
75 | subs.add atom.commands.add 'atom-workspace',
76 | 'julia-client:open-a-repl': => terminal.repl()
77 | 'julia-client:start-repl-client': =>
78 | client.disrequire =>
79 | tcp.listen (port) => terminal.client port
80 | 'julia-client:start-julia': =>
81 | client.disrequire =>
82 | tcp.listen (port) => process.start port, cons
83 | 'julia-client:toggle-console': => @withInk => cons.toggle()
84 | 'julia-client:reset-loading-indicator': => client.reset()
85 |
86 | subs.add atom.commands.add 'atom-text-editor[data-grammar="source julia"]:not([mini])',
87 | 'julia-client:set-working-module': => modules.chooseModule()
88 | 'julia-client:reset-working-module': => modules.resetModule()
89 |
90 | utils.commands subs
91 |
92 | consumeInk: (ink) ->
93 | @ink = ink
94 | evaluation.ink = ink
95 | cons.ink = ink
96 | @loading = new ink.Loading
97 | client.loading = @loading
98 | cons.loading = @loading
99 | @spinner = new ink.Spinner client.loading
100 | client.handle 'show-block', ({start, end}) =>
101 | if ed = atom.workspace.getActiveTextEditor()
102 | ink.highlight ed, start-1, end-1
103 |
104 | withInk: (f, err) ->
105 | if @ink?
106 | f()
107 | else if err
108 | atom.notifications.addError 'Please install the Ink package.',
109 | detail: 'Julia Client requires the Ink package to run.
110 | You can install it from the settings view.'
111 | dismissable: true
112 | else
113 | setTimeout (=> @withInk f, true), 100
114 |
115 | consumeStatusBar: (bar) -> modules.consumeStatusBar(bar)
116 |
117 | completions: -> completions
118 |
--------------------------------------------------------------------------------
/lib/modules.coffee:
--------------------------------------------------------------------------------
1 | client = require './connection/client'
2 | selector = require './ui/selector'
3 | {CompositeDisposable} = require 'atom'
4 |
5 | module.exports =
6 | activate: ->
7 | @subscriptions = new CompositeDisposable
8 | @createStatusUI()
9 |
10 | # configure all the events that persist until we're deactivated
11 | @subscriptions.add atom.workspace.onDidChangeActivePaneItem => @activePaneChanged()
12 | @subscriptions.add client.onConnected => @editorStateChanged()
13 | @subscriptions.add client.onDisconnected => @editorStateChanged()
14 | @activePaneChanged()
15 |
16 | deactivate: ->
17 | @tile?.destroy()
18 | @tile = null
19 | @subscriptions.dispose()
20 | @grammarChangeSubscription?.dispose()
21 | @moveSubscription?.dispose()
22 | if @pendingUpdate then clearTimeout @pendingUpdate
23 |
24 |
25 | activePaneChanged: ->
26 | @grammarChangeSubscription?.dispose()
27 | ed = atom.workspace.getActivePaneItem()
28 | @grammarChangeSubscription = ed?.onDidChangeGrammar? => @editorStateChanged()
29 | @editorStateChanged()
30 |
31 | # sets or clears the callback on cursor change based on the editor state
32 | editorStateChanged: ->
33 | # first clear the display and remove any old subscription
34 | @clear()
35 | @moveSubscription?.dispose()
36 |
37 | # now see if we need to resubscribe
38 | ed = atom.workspace.getActivePaneItem()
39 | if ed?.getGrammar?().scopeName == 'source.julia' && client.isConnected()
40 | @moveSubscription = ed.onDidChangeCursorPosition =>
41 | if @pendingUpdate then clearTimeout @pendingUpdate
42 | @pendingUpdate = setTimeout (=> @update()), 300
43 | @update()
44 |
45 | # Status Bar
46 |
47 | createStatusUI: ->
48 | @dom = document.createElement 'span'
49 | @dom.classList.add 'julia-client'
50 | @main = document.createElement 'a'
51 | @sub = document.createElement 'span'
52 | @divider = document.createElement 'span'
53 | @divider.innerText = '/'
54 |
55 | @main.onclick = =>
56 | atom.commands.dispatch atom.views.getView(atom.workspace.getActiveTextEditor()),
57 | 'julia-client:set-working-module'
58 |
59 | atom.tooltips.add @dom,
60 | title: => "Currently working with module #{@currentModule()}"
61 |
62 | currentModule: ->
63 | if @isInactive then "Main"
64 | else if @isSubInactive || @sub.innerText == "" then @main.innerText
65 | else "#{@main.innerText}.#{@sub.innerText}"
66 |
67 | consumeStatusBar: (bar) ->
68 | @tile = bar.addRightTile {item: @dom}
69 |
70 | clear: -> @dom.innerHTML = ""
71 |
72 | reset: (main, sub) ->
73 | @clear()
74 | @main.innerText = main
75 | @sub.innerText = sub
76 | @dom.appendChild @main
77 | if sub
78 | @dom.appendChild @divider
79 | @dom.appendChild @sub
80 | @active()
81 |
82 | isSubInactive: false
83 | isInactive: false
84 |
85 | active: ->
86 | @isInactive = false
87 | @isSubInactive = false
88 | @main.classList.remove 'fade'
89 | @divider.classList.remove 'fade'
90 | @sub.classList.remove 'fade'
91 | subInactive: ->
92 | @active()
93 | @isSubInactive = true
94 | @sub.classList.add 'fade'
95 | inactive: ->
96 | @active()
97 | @isInactive = true
98 | @main.classList.add 'fade'
99 | @divider.classList.add 'fade'
100 | @sub.classList.add 'fade'
101 |
102 | # gets the current module from the Julia process and updates the display.
103 | # assumes that we're connected and in a julia file
104 | update: ->
105 | ed = atom.workspace.getActivePaneItem()
106 | {row, column} = ed.getCursors()[0].getScreenPosition()
107 | data =
108 | path: ed.getPath()
109 | code: ed.getText()
110 | row: row+1, column: column+1
111 | module: ed.juliaModule
112 |
113 | client.msg 'module', data, ({main, sub, inactive, subInactive}) =>
114 | @reset main, sub
115 | subInactive && @subInactive()
116 | inactive && @inactive()
117 |
118 | # TODO: auto detect option, remove reset command
119 | chooseModule: ->
120 | client.require =>
121 | mods = new Promise (resolve) =>
122 | client.msg 'all-modules', {}, (mods) =>
123 | resolve mods
124 | selector.show mods, (mod) =>
125 | return unless mod?
126 | atom.workspace.getActiveTextEditor().juliaModule = mod
127 | @update()
128 |
129 | resetModule: ->
130 | client.require =>
131 | delete atom.workspace.getActiveTextEditor().juliaModule
132 | @update()
133 |
--------------------------------------------------------------------------------
/docs/communication.md:
--------------------------------------------------------------------------------
1 | # Communication
2 |
3 | Juno works by booting a Julia client from Atom. When Julia starts it connects to Atom over a
4 | TCP port, and the upshot is that from that point on Julia and Atom can each send messages to
5 | each other. Messages are JSON objects, with a type header to tell the receiver how the
6 | message should be handled.
7 |
8 | The code handling low-level communication is kept in
9 | [client.coffee](https://github.com/JunoLab/atom-julia-client/blob/master/lib/connection/client.coffee)
10 | and [comm.jl](https://github.com/JunoLab/Atom.jl/blob/master/src/comm.jl). However, the
11 | details of those files aren't particularly important – you only need to understand the
12 | communication API, which we'll go over here.
13 |
14 | ## Sending messages from Atom
15 |
16 | Communication works by sending messages with an appropriate type on one side and registering
17 | handlers for that type on the other. The handler then takes some action and returns data to
18 | the original sender. For example, on the Atom side messages are sent in CoffeeScript as
19 | follows:
20 |
21 | ```coffeescript
22 | client.msg 'eval', {code: '2+2'}
23 | ```
24 |
25 | On the Julia side, we need to set up a handler for this message, which happens as follows:
26 |
27 | ```julia
28 | handle("eval") do data
29 | Dict(:result => eval(parse(data["code"])))
30 | end
31 | ```
32 |
33 | This is a very simplified version of the `eval` handler that you can find in the Atom.jl
34 | source code. It simply evaluates whatever it's given and returns the result – in this case,
35 | `4` – inside a dictionary.
36 |
37 | Often we want to do something with that return result in Atom – in this case, we'd like to
38 | display the result. We don't need to change anything on the Julia side to accomplish this;
39 | it's as simple as adding a callback to the original `msg` call:
40 |
41 | ```coffeescript
42 | client.msg 'eval', {code: '2+2'}, ({result}) =>
43 | console.log data
44 | ```
45 |
46 | This call sends the `eval` message, pulls the `result` field out of the returned JSON, and
47 | displays the result, `4`, in the console.
48 |
49 | This approach is exactly how Atom gets evaluation results, autocompletion and more from
50 | Julia – so it's easy to find more examples spread throughout the
51 | [julia-client](https://github.com/JunoLab/atom-julia-client/tree/master/lib) and
52 | [Atom.jl](https://github.com/JunoLab/Atom.jl/tree/master/src) source code.
53 |
54 | As a first project, try implementing an Atom command (see the Atom docs) which sends this
55 | message to Julia, as well as adding the Julia handler above to Atom.jl. (You'll want to use
56 | a type other than `eval` to avoid clashes with actual evaluation.)
57 |
58 | ## Sending messages from Julia
59 |
60 | Julia has a similar mechanism to talk to Atom via the function
61 |
62 | ```julia
63 | msg("type", data)
64 | ```
65 |
66 | Handlers are defined on the Atom side as follows:
67 |
68 | ```coffeescript
69 | client.handle 'type', (data) =>
70 | console.log data
71 | ```
72 |
73 | It's also possible for Julia to wait for a response from Atom, using the `rpc` function.
74 | An extra argument is provided to the JS handler which can be called with the data to send
75 | back to Julia. To clarify this a little, say you have a handler in Atom which looks like
76 | this:
77 |
78 | ```coffeescript
79 | client.handle 'echo', (data, resolve) =>
80 | resolve data
81 | ```
82 |
83 | (It's very easy to add this code to `julia-client`'s [`activate`
84 | function](https://github.com/JunoLab/atom-julia-client/blob/master/lib/julia-client.coffee)
85 | if you want to try this out.)
86 |
87 | Calling the following from the REPL:
88 |
89 | ```julia
90 | Atom.rpc("echo", Dict(:a=>1, :b=>2))
91 | ```
92 |
93 | will return `Dict("a"=>1, "b"=>2)`. The data was passed to Atom and simply returned as-is.
94 | Try changing the handler to modify the data before returning it.
95 |
96 | This mechanism is how Julia commands like `Atom.select()` are implemented, and in general it
97 | makes it very simple for Julia to control the Atom frontend – see
98 | [frontend.jl](https://github.com/JunoLab/Atom.jl/blob/master/src/frontend.jl) and
99 | [frontend.coffee](https://github.com/JunoLab/atom-julia-client/blob/master/lib/frontend.coffee)
100 |
101 | ## Debugging and Learning
102 |
103 | A good way to get a handle on this stuff is just to use `console.log` and `@show`, on the
104 | Atom and Julia sides respectively, to take a peek at what's going over the wire. For example,
105 | it's easy to change the above Julia handler to
106 |
107 | ```julia
108 | handle("eval") do data
109 | @show data
110 | @show Dict(:result => eval(parse(data["code"])))
111 | end
112 | ```
113 |
114 | This will show you both the data being sent to Julia (in the example above,
115 | `Dict("code"=>"2+2")`) and the data being sent back to Atom (`Dict(:result => 4)`).
116 | Modifying say, the completions handler in a similar way will show you what completion data
117 | Julia sends back to Atom (there will probably be a lot, so try looking at specific
118 | keys, for example).
119 |
120 | You don't need to reload Atom or restart the Julia client every time you make a change like
121 | this. If you open a file from the `Atom.jl` source code, you should see from the status bar
122 | that Juno knows you're working with the `Atom` module (try evaluating `current_module()` if
123 | you're not sure). Evaluating `handlers` from within the `Atom` module will show you what
124 | message types are currently defined. If you change a handler, just press `C-Enter` to update
125 | it in place; you should see the effect of your update immediately next time the handler is
126 | triggered. For example, if you modify the
127 | [`eval`](https://github.com/JunoLab/Atom.jl/blob/master/src/eval.jl) handler as follows:
128 |
129 | ```julia
130 | handle("eval") do data
131 | println(data["code"]) # <- insert this line
132 | # ...
133 | ```
134 |
135 | and update it, you should find that the *next* time you evaluate you see the contents of the
136 | current editor dumped into the console. Thus, most features or fixes you'd want to add to
137 | Juno can be made without a long edit – compile – run cycle.
138 |
--------------------------------------------------------------------------------
/docs/workflow.md:
--------------------------------------------------------------------------------
1 |
5 |
6 | # Workflow
7 |
8 | One of the goals of this project is to help users adopt a more efficient
9 | workflow when editing Julia code. To understand how to gain this productivity
10 | boost, we will consider a common workflow for many Julia developers and a Juno
11 | based workflow.
12 |
13 | ## Status quo
14 |
15 | Suppose you are working on a file `foo.jl` that contains the definition of a
16 | type `Bar`. As you are hacking away on the fields and functionality of the `Bar`
17 | type you want to experiment with your work in the REPL. Here's what you might do:
18 |
19 | - Edit `foo.jl`
20 | - Fire up Julia and `include("foo.jl")` to load up your new types and methods
21 | - Return to the editor to make more changes to `foo.jl`
22 | - Go back to your REPL session and `include("foo.jl")` again, only to be greeted
23 | by the following error message:
24 |
25 | ```
26 | julia> include("foo.jl")
27 | ERROR: LoadError: invalid redefinition of constant Bar
28 | in include at ./boot.jl:260
29 | in include_from_node1 at ./loading.jl:271
30 | while loading /Users/sglyon/Desktop/temp/julia/junk/foo.jl, in expression starting on line 3
31 | ```
32 |
33 | At this point you have two options:
34 |
35 | 1. Quit Julia completely and start over. Then we will be able to
36 | `include("foo.jl")` one time before seeing that error message again.
37 | 2. Follow the [workflow tips](http://docs.julialang.org/en/latest/manual/workflow-tips/#repl-based-workflow)
38 | section of the manual and wrap the code in a module.
39 |
40 | Option 1 is painful for obvious reasons: every time we make a change to anything
41 | in `foo.jl` we have to restart Julia and reload everything associated with our
42 | code (including other packages we are `using`).
43 |
44 | Option 2 seems better: it allows us to continue working with the same REPL
45 | instance and reload code as we need to. However, it also comes with some costs:
46 |
47 | - We now have to prefix all REPL access to anything in `foo.jl` with the name
48 | of our module: i.e. we use `ModuleName.Bar` instead of just `Bar`.
49 | - Although we can re`include` our module, we will be having Julia reload the
50 | entire module, rather than just the parts that we have changed.
51 |
52 | Wouldn't it be great if we had a solution that allowed:
53 |
54 | - Reloading code at will
55 | - Not prefixing everything with a module name
56 | - Only reloading the code that actually changes
57 | - Avoiding the need to switch back and forth from editor to REPL (a bonus :+1:)
58 |
59 | ## Enter Juno
60 |
61 | Juno provides a way to do just that. I will first describe how the Juno workflow
62 | looks and then explain a few of the details that make it possible:
63 |
64 | - Start by putting the code from `foo.jl` in a module (as in option 2 above).
65 | Here we use the name `Tmp`.
66 | - Launch a Julia client by executing the command `Julia client: Start Julia`
67 | (`cmd-j cmd-s` on OSX `ctrl-j ctrl-s` otherwise).
68 | - Now, while editing `foo.jl` you should see `Main/Tmp` on the status
69 | bar as shown in the screenshot below:
70 |
71 | 
72 |
73 | - If `foo.jl` is not on the Julia `LOAD_PATH` you should evaluate the
74 | whole file by executing the `Julia client: evaluate all` command
75 | (`cmd-shift-enter` or `ctrl-shift-enter`)
76 | - Now open a different buffer and set the syntax to Julia (`ctrl+shift+l` on all
77 | platforms and select `Julia`). While this buffer is active run the
78 | `Julia Client: Set working module` command (`cmd-j cmd-m` or `ctrl-j ctrl-m`)
79 | and select the name of the module defined in `foo.jl`. After doing this you
80 | should see `Tmp` on the status bar where `Main/Tmp` was:
81 |
82 | 
83 |
84 | - Now you are prepared to treat this buffer like the Julia REPL by writing code
85 | and evaluating it using `cmd-enter` (or `ctrl-enter` on Linux or Windows).
86 | - The key difference is that this "REPL" will have _unqualifed_ access to
87 | all members of `Tmp`: i.e. you can use `Bar` instead of `ModuleName.Bar`
88 | as was required in option 2 above.
89 | - What's more is that you can go back to `foo.jl`, edit and evaluate method
90 | definitions (again using `cmd-enter` or `ctrl-enter`) and the new methods
91 | are automatically ready to go when you return to the second buffer
92 |
93 | ### How it works
94 |
95 | So, how does this magic work? The secret sauce used here is clever use of an
96 | optional argument to the `Base.eval` method. Let's look at the `?help` entry for
97 | `Base.eval`:
98 |
99 | ```
100 | help?> Base.eval
101 | eval([m::Module], expr::Expr)
102 |
103 | Evaluate an expression in the given module and return the result. Every Module (except those defined with baremodule) has its own 1-argument definition of eval, which evaluates expressions in that
104 | module.
105 | ```
106 |
107 | Notice that if one argument is given, `Expr` is evaluated in the current working
108 | module. However, if two arguments are given the first argument gives the name of
109 | the module in which the `Expr` should be evaluated. So, when you run `Julia
110 | Client: Set working module` what you are doing is telling Atom to evaluate all
111 | the code from the current buffer directly into the chosen module. In our example
112 | above, this means that all the code we evaluate from `scratch.jl` is evaluated
113 | within the `Tmp` module _as if_ we had put the code in the module to begin with.
114 |
115 | ### Pro Tips
116 |
117 | This workflow can take some getting used to, but can yield massive productivity
118 | boosts once you master it. Here are some tips to help as you learn:
119 |
120 | - Don't ever set the current working module to the module defined in the buffer
121 | you are working on.
122 | - In the example above this means that if we wouldn't set the module to `Tmp`
123 | from within `foo.jl`.
124 | - Doing so would show `Tmp/Tmp` in the status bar and would make it awkward
125 | to re-evaluate the entire buffer later on.
126 | - If you do get in this situation, set the current working module to `Main`
127 | to restore `Main/Tmp` in the status bar
128 | - You can't change a type definition and re-evaluate with `cmd-enter` (or
129 | `ctrl-enter`). Instead, whenever you modify a type definition you should
130 | re-evaluate whole buffer using `cmd-shift-enter` (`ctrl-shift-enter`).
131 | - In the description of the workflow we mentioned opening up throwaway buffer
132 | and simply setting the grammar (active syntax) to Julia. Here are two
133 | alternatives:
134 | 1. You could treat the main file itself (`foo.jl` in the example) as the
135 | REPL. This saves you from having to set the working module in a
136 | different buffer, but also means you probably need to do some cleanup
137 | when you are done.
138 | 2. Instead of throwing away the second buffer, you could save it as a
139 | scratch file (I create files named `scratch.jl`). This file then becomes
140 | like a persistent REPL where test code can survive across coding sessions.
141 | - This can be helpful when you are working on the same code over
142 | multiple work sessions as it means you do not have to construct
143 | intermediate test objects from nothing each time.
144 | - Using this workflow is especially helpful when working on `Base` Julia. As you
145 | make changes to the files you don't need to re-build Julia to interact with
146 | them. The same logic applies to other large modules and packages.
147 | - While Juno understands how to deal with dependence on the current working
148 | directory in calls to functions like `include`, sometimes it is still useful
149 | to have the Julia instance work from the directory of your main file (e.g.
150 | when you are writing output files to disk)
151 | - The commands `Julia Client: Work in File Folder` will set the working
152 | directory for the Julia process to be the directory where your file is
153 | contained
154 | - I bind this function to a keybinding in my `~/.atom/keymaps.cson` to make
155 | this more convenient:
156 | ```coffeescript
157 | 'atom-text-editor[data-grammar="source julia"]':
158 | 'cmd-j cmd-f': 'julia-client:work-in-file-folder'
159 | ```
160 | - There are also commands `Julia Client: Work in Project Folder` and
161 | `Julia Client: Work in Home Folder` that can set the directory for the
162 | Julia process.
163 |
--------------------------------------------------------------------------------