├── .biscottoopts ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── docs └── main.gif ├── keymaps └── rhino-python.cson ├── lib ├── rhino-autocomplete-plus-python-provider.coffee ├── rhino-python.coffee ├── rhino-settings-view.coffee └── talk-to-rhino.coffee ├── menus └── rhino-python.cson ├── package-lock.json ├── package.json ├── spec ├── rhino-autocomplete-plus-python-provider-spec.coffee └── rhino-python-spec.coffee └── styles └── rhino-python.less /.biscottoopts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcneel/rhino-python/4d9a2c48b681e1120744ca5a424a657084a0f9cd/.biscottoopts -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | HelloRhino.py 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | HelloRhino.py 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.0 - First Release 2 | * Every feature added 3 | * Every bug fixed 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rhino-python 2 | 3 | Code completion support for your Python scripts that are executed by [Rhinoceros for Mac] (https://www.rhino3d.com/mac). 4 | 5 | ![rhino-python](https://raw.githubusercontent.com/mcneel/rhino-python/master/docs/main.gif) 6 | 7 | 8 | ## Installation 9 | 10 | - Download and install the latest [Rhino 5 for Mac][2] 11 | - Download and install the [Atom text editor][1] 12 | - From the "Atom" menu select "Install Shell Commands" in case they are not already installed. 13 | - Press the `cmd` + `,` to launch the Settings view. 14 | - Click on the Install tab and then click on the "Packages" button right of the top edit box. 15 | - Type "rhino-python" in the "Search Packages" edit box followed by the `Enter` key. 16 | - The rhino-python package will be the 1st one in the list. Click the "Install" button. 17 | 18 | ## Quick Start 19 | 20 | - From Rhino run the **```StartAtomEditorListener```** command. 21 | - Launch the Atom editor and save the "untitled" document as mypythonscript.py. The file needs to have a ".py" extension for the rhino-python package to recognize it. 22 | - Type **```import ```** (followed by a trailing space). Depending on how you have Atom configured and which other packages you have installed you'll see a completion window with suggestions on what to type next. Type "rh" to start filtering the list and notice that the suggestions provided by Rhino are labeled with the "<- Rhino" hint on the right of the suggestion. 23 | - As you continue typing **```rhinos```** you'll see the completion data list get filtered until the only option left is "rhinoscriptsyntax". Press the `tab` key (or `enter` if configured that way) to accept it. Finish typing the line: 24 | **```import rhinoscriptsyntax as rs```** 25 | - On the next line type **```from Rhino.Geometry import Point3d```**. The completion data will pop up for **```Rhino```**, **```Geometry```**, and **```Point3d```**. Press the `tab` key (or `enter` if configured that way) to accept each. 26 | - For the next line type **```rs.AddCircle(Point3d.Origin, 5.0)```** and again notice the completion window after each `.` and the doc string panel after the `(`. Keep typing until the desired data is highlighted in the completion window and press the `tab` (or `enter` if configured that way) key to accept. 27 | - To send the file to Rhino for execution press the `ctrl` + `alt` + `r` keys. 28 | 29 | ## Manage Python Search Paths (supported by version 5.2 WIP 5C41w and later) 30 | 31 | Press the `ctrl` + `alt` + `s` keys to open the Rhino Python Search Paths panel. 32 | 33 | - When the window is first opened only the add `+` button is enabled. 34 | - After adding one or more pates click on one to select it and the other nav buttons become enabled. This doesn't apply to the default paths (system paths) because they cannot be edited. 35 | - None of the changes made are saved until the save button is clicked which sends the save request to Rhino. 36 | - Clicking the revert button discards all changes made since the last save. 37 | 38 | ## Autocomplete Plus configuration and additional notes 39 | 40 | Experiment with the Autocomplete Plus settings to fine tune your setup: 41 | 42 | - Press the `cmd` + `,` keys to open the Settings panel. 43 | - Click on the Packages tab and type "autocomplete plus" in the "Filter packages by name" edit box. 44 | - Click on the "Settings" button of the autocomplete-plus package and experiment with the settings. 45 | 46 | Because completion data can come from many providers if you installed other packages or if you have the "Enable Built-In Provider" checked in Autocomplete Plus settings, the data coming from rhino is labeled "`<- Rhino`" (right-most column of completion window). 47 | 48 | ## Get Involved 49 | - [Rhino Developer Docs] (http://developer.rhino3d.com) 50 | - More about [scripting] (http://developer.rhino3d.com/guides/#rhinopython) in Rhino 51 | - Join discussions on our [forum](http://discourse.mcneel.com) 52 | - Report Issues on our [youtrack](http://mcneel.myjetbrains.com/youtrack/dashboard) site 53 | 54 | 55 | [1]: https://atom.io 56 | [2]: http://https://www.rhino3d.com/download/rhino-for-mac/5.0/wip 57 | -------------------------------------------------------------------------------- /docs/main.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcneel/rhino-python/4d9a2c48b681e1120744ca5a424a657084a0f9cd/docs/main.gif -------------------------------------------------------------------------------- /keymaps/rhino-python.cson: -------------------------------------------------------------------------------- 1 | # Keybindings require three things to be fully defined: A selector that is 2 | # matched against the focused element, the keystroke and the command to 3 | # execute. 4 | # 5 | # Below is a basic keybinding which registers on all platforms by applying to 6 | # the root workspace element. 7 | 8 | # For more detailed documentation see 9 | # https://atom.io/docs/latest/advanced/keymaps 10 | 'atom-workspace': 11 | 'ctrl-alt-r': 'rhino-python:saveAndRunInRhino' 12 | 'shift-ctrl-space': 'autocomplete-plus:activate' 13 | 'ctrl-alt-s': 'rhino-python:toggleRhinoSettingsView' 14 | -------------------------------------------------------------------------------- /lib/rhino-autocomplete-plus-python-provider.coffee: -------------------------------------------------------------------------------- 1 | $ = require "jquery" 2 | _ = require 'underscore' 3 | fuzz = require "fuzzaldrin" 4 | {MessagePanelView, LineMessageView, PlainMessageView} = require 'atom-message-panel' 5 | ttr = require './talk-to-rhino' 6 | 7 | module.exports = 8 | class RhinoProvider 9 | selector: '.source.python' 10 | disableForSelector: '.source.python .comment' 11 | inclusionPriority: 1 12 | excludeLowerPriority: true 13 | @cachedSuggestions = [] 14 | @callRhinoPosition = null 15 | 16 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 17 | prefix = if /\s$/.test prefix then ' ' else prefix 18 | lines = editor.buffer.getLines()[0..bufferPosition.row] 19 | return [] unless lines.length 20 | cursorLine = lines[lines.length-1][0..bufferPosition.column-1] 21 | return [] unless cursorLine? 22 | callRhinoPosition = @getCallRhinoPosition cursorLine, lines.length-1 23 | return [] unless callRhinoPosition 24 | lineLeftOfCallRhinoPosition = lines[lines.length-1][0..callRhinoPosition.column] 25 | lines = if lines.length is 1 then [] else lines[0..lines.length-2] 26 | 27 | if (@positionsAreEqual callRhinoPosition, RhinoProvider.callRhinoPosition) and (not @stringIsCallRhinoChar prefix) 28 | return @filterCachedSuggestions prefix 29 | else 30 | @clearCache() 31 | if @stringIsOpenParen prefix 32 | lines.push cursorLine 33 | @getAndShowDocString({editor, bufferPosition}, lines) 34 | return [] 35 | lines.push lineLeftOfCallRhinoPosition 36 | return ttr.getCompletionData lines, callRhinoPosition, editor.getPath(), 37 | if @stringIsCallRhinoChar prefix then null else prefix, 38 | @clearCache, 39 | (p, s) -> RhinoProvider.callRhinoPosition = p; RhinoProvider.cachedSuggestions = s, 40 | (pfx) => @filterCachedSuggestions pfx 41 | 42 | filterCachedSuggestions: (prefix) -> 43 | suggestions = fuzz.filter RhinoProvider.cachedSuggestions, prefix, key: 'text' 44 | return suggestions.map (s) -> {text: s.text, replacementPrefix: prefix, rightLabelHTML: s.rightLabelHTML} 45 | 46 | stringIsCallRhinoChar: (s) -> 47 | /^[\s\.(]$/.test s 48 | stringIsOpenParen: (s) -> 49 | /^[(]$/.test s 50 | 51 | clearCache: -> 52 | RhinoProvider.cachedSuggestions = [] 53 | RhinoProvider.callRhinoPosition = null 54 | 55 | getCallRhinoPosition: (line, row) -> 56 | m = line.match /[\s\.(][a-zA-Z0-9_-]*$/ 57 | if m then {row: row, column: m.index} else null 58 | 59 | positionsAreEqual: (p1, p2) -> 60 | return false unless p1 and p2 61 | p1.row is p2.row and p1.column is p2.column 62 | 63 | getAndShowDocString: (options, lines) -> 64 | ttr.getDocString options, lines 65 | .done (ds) -> 66 | RhinoProvider.showDocString ds 67 | 68 | # todo: UI stuff doesn't belong here 69 | messages: null 70 | @showDocString: (ds) -> 71 | if @messages? 72 | @messages.close() 73 | @messages = null 74 | 75 | [first, ...] = ds.split '\n' 76 | rest = ds.split('\n')[2..] 77 | 78 | #
79 | #
doh
80 | #
81 | 82 | # convert ' ' and '|' to   83 | rest = _.map rest, (s) -> (_.map s, (c) -> if c is ' ' or c is '|' then ' ' else c).join("") 84 | # remove empty lines 85 | rest = _.reject rest, (s) -> /^(\s| )*$/.test s 86 | 87 | rest_html = _.map rest, (ln) -> """
#{ln}
""" 88 | msg = """
#{rest_html.join('\n')}
""" 89 | #msg = """
""" 90 | 91 | @messages = new MessagePanelView 92 | title: first 93 | 94 | @messages.clear() 95 | @messages.add new PlainMessageView 96 | #message: "#{rest.join('\n')}""" 98 | message: msg 99 | raw: true 100 | #className: 'panel-body padded' 101 | @messages.attach() 102 | 103 | dispose: -> 104 | console.log 'dispose rhino-python' 105 | -------------------------------------------------------------------------------- /lib/rhino-python.coffee: -------------------------------------------------------------------------------- 1 | $ = require "jquery" 2 | shelljs = require "shelljs" 3 | ttr = require './talk-to-rhino' 4 | {CompositeDisposable} = require 'atom' 5 | RhinoSettingsView = require './rhino-settings-view' 6 | Vue = require 'vue' 7 | remote = require 'remote' 8 | dialog = remote.dialog 9 | _ = require 'underscore' 10 | 11 | module.exports = 12 | config: 13 | httpPort: 14 | title: 'Port Number' 15 | description: 'Port number that Rhino listens on for http requests. NOTE: has to match the port number configured in Rhinoceros.' 16 | type: 'integer' 17 | default: 8080 18 | 19 | provider: null 20 | ready: false 21 | 22 | _indexOf: (coll, item) -> 23 | _.indexOf(coll, item) 24 | 25 | activate: (state) -> 26 | @ready = true 27 | 28 | # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable 29 | @subscriptions = new CompositeDisposable 30 | 31 | @subscriptions.add atom.commands.add 'atom-workspace', 'rhino-python:saveAndRunInRhino': => @saveAndRunInRhino() 32 | @subscriptions.add atom.commands.add 'atom-workspace', 'rhino-python:saveAndRunInRhinoFromTreeView': => @saveAndRunInRhinoFromTreeView() 33 | 34 | @rhinoSettingsView = new RhinoSettingsView(state.rhinoSettigsViewState) 35 | @modalPanel = atom.workspace.addBottomPanel(item: @rhinoSettingsView.getElement(), visible: false) 36 | @subscriptions.add atom.commands.add 'atom-workspace', 'rhino-python:toggleRhinoSettingsView': => @toggleRhinoSettingsView() 37 | 38 | @v = new Vue({ 39 | el: '#RhinoSettingsView' 40 | methods: 41 | add: -> 42 | path_to_add = dialog.showOpenDialog({properties:['openDirectory']}) 43 | unless path_to_add? 44 | return 45 | path_to_add = path_to_add[0] 46 | #dup_path = _.find(@paths, (p) -> p.path == path_to_add[0]) 47 | for p in @paths 48 | if p.path == path_to_add 49 | dup_path = path_to_add 50 | unless dup_path? 51 | len = @paths.push({path: path_to_add, selected: false, type: "user"}) 52 | @dirty = true 53 | #@select _.last(@paths) 54 | @select @paths[len-1] 55 | delete: -> 56 | unless @aPathIsSelected()? 57 | # this should never happen 58 | alert 'no path is selected' 59 | sp = @selectedPath() 60 | #newps = _.reject(@paths, (p) -> p.path == sp.path) 61 | #@paths = newps 62 | idx = -1 63 | `for (i = 0; i < this.paths.length; i++) { 64 | if (this.paths[i].path == sp.path) { 65 | idx = i; 66 | } 67 | }` 68 | #newps = _.reject(@paths, (p) -> p.path == sp.path) 69 | @paths.splice(idx, 1) # delete from array 70 | @dirty = true 71 | @setBtnEnabled() 72 | moveUp: -> @move('up') 73 | moveDown: -> @move('down') 74 | move: (upOrDown) -> 75 | if upOrDown == 'up' and @selectedIsFirst() 76 | return 77 | if upOrDown == 'down' and @selectedIsLast() 78 | return 79 | @dirty = true 80 | sp = @selectedPath() 81 | #idx = _.indexOf(@paths, sp) 82 | idx = -1 83 | `for (i = 0; i < this.paths.length; i++) { 84 | if (this.paths[i].path == sp.path) { 85 | idx = i; 86 | } 87 | }` 88 | #newps = _.reject(@paths, (p) -> p.path == sp.path) 89 | @paths.splice(idx, 1) # delete from array 90 | newidx = if upOrDown == 'up' then --idx else ++idx 91 | @paths.splice(newidx, 0, sp) # add item in new position 92 | @setBtnEnabled() 93 | show: -> alert 'show!' 94 | save: -> 95 | ttr.setPythonSearchPaths(@paths, @restartEngineChecked, (response) => 96 | if /^ok/.test response 97 | @dirty = false 98 | @setBtnEnabled() 99 | ) 100 | revert: -> 101 | ttr.getPythonSearchPaths((psp) => 102 | @paths = psp 103 | @dirty = false 104 | @setBtnEnabled() 105 | ) 106 | select: (path) -> 107 | #_.each(_.filter(@paths, (i) -> i.selected == true), (p) -> p.selected = false) 108 | unless path.type == "system" 109 | for p in @paths 110 | if p.selected == true 111 | p.selected = false 112 | path.selected = true 113 | @setBtnEnabled() 114 | setBtnEnabled: -> 115 | @disableAllBtnsExceptAdd() 116 | if @aPathIsSelected() 117 | @deleteDisabled = false 118 | @showDisabled = false 119 | if @selectedIsNotFirst() 120 | @upDisabled = false 121 | if @selectedIsNotLast() 122 | @downDisabled = false 123 | if @dirty 124 | @saveDisabled = false 125 | @revertDisabled = false 126 | 127 | disableAllBtnsExceptAdd: -> 128 | @deleteDisabled = true 129 | @upDisabled = true 130 | @downDisabled = true 131 | @showDisabled = true 132 | @saveDisabled = true 133 | @revertDisabled = true 134 | 135 | aPathIsSelected: -> 136 | #can't access the underscore lib from here? 137 | #_.any(@paths, (p) -> p.selected) 138 | apis = false 139 | for p in @paths 140 | if p.selected 141 | apis = true 142 | apis 143 | 144 | selectedPath: -> 145 | #_.find(@paths, (p) -> p.selected) 146 | for p in @paths 147 | if p.selected 148 | sp = p 149 | sp 150 | 151 | systemPathsCount: -> 152 | c = 0; 153 | `for (i = 0; i < this.paths.length; i++) { 154 | if (this.paths[i].type == 'system') { 155 | c++; 156 | } 157 | }` 158 | return c 159 | 160 | selectedIsFirst: -> 161 | #@aPathIsSelected() and @selectedPath().path == _.first(@paths).path 162 | @aPathIsSelected() and @selectedPath().path == @paths[@systemPathsCount()].path 163 | selectedIsNotFirst: -> 164 | not @selectedIsFirst() 165 | 166 | selectedIsLast: -> 167 | #@aPathIsSelected() and @selectedPath().path == _.last(@paths).path 168 | @aPathIsSelected() and @selectedPath().path == @paths[@paths.length-1].path 169 | selectedIsNotLast: -> 170 | not @selectedIsLast() 171 | data: 172 | dirty: false 173 | deleteDisabled: true 174 | upDisabled: true 175 | downDisabled: true 176 | showDisabled: true 177 | saveDisabled: true 178 | revertDisabled: true 179 | restartEngineChecked: false 180 | paths: [] 181 | }) 182 | 183 | toggleRhinoSettingsView: -> 184 | if @modalPanel.isVisible() 185 | if @v.dirty 186 | alert "Rhino Python Search Paths: have unresolved changes. Save or revert them before closing." 187 | return 188 | @modalPanel.hide() 189 | else 190 | if @v.dirty 191 | alert "Closed settings view is dirty. This should never happen." 192 | ttr.rhinoIsListening() 193 | .done (isListening) => 194 | [isListening, _] = isListening 195 | if isListening 196 | ttr.getPythonSearchPaths((psp) => 197 | @v.paths = psp 198 | @v.dirty = false 199 | @v.setBtnEnabled() 200 | @modalPanel.show() 201 | ) 202 | .fail (errorObject) -> 203 | alert "Rhino isn't listening for requests. Run the \"StartAtomEditorListener\" command from within Rhino." 204 | 205 | deactivate: -> 206 | @provider = null 207 | @subscriptions.dispose() 208 | 209 | provide: -> 210 | unless @provider? 211 | RhinoProvider = require('./rhino-autocomplete-plus-python-provider') 212 | @provider = new RhinoProvider() 213 | @provider 214 | 215 | saveAndRunInRhino: -> 216 | ttr.rhinoIsListening() 217 | rhinoIsntListeningMsg = "Rhino isn't listening for requests. Run the \"StartAtomEditorListener\" command from within Rhino." 218 | editor = atom.workspace.getActiveTextEditor() 219 | if editor and not /.py$/.test editor.getPath() 220 | alert("Can't save and run. Not a python file.") 221 | return 222 | editor.save() 223 | ttr.rhinoIsListening() 224 | .done (isListening) => 225 | [isListening, rhinoPath] = isListening 226 | if isListening 227 | @bringRhinoToFront(rhinoPath) 228 | ttr.runInRhino editor.getPath() 229 | .done (msg) -> 230 | console.log msg 231 | .fail (errorObject) -> 232 | alert(rhinoIsntListeningMsg) 233 | else 234 | alert(rhinoIsntListeningMsg) 235 | .fail (errorObject) -> 236 | alert(rhinoIsntListeningMsg) 237 | 238 | bringRhinoToFront: (rhinoPath) -> 239 | rhino = shelljs.exec("open \"#{rhinoPath}\"", async: true, (code, output) -> 240 | console.log "bringRhinoToFront: output: #{output}" unless not output 241 | ) 242 | 243 | saveAndRunInRhinoFromTreeView: -> 244 | #s = document.querySelectorAll('[is="tree-view-file"] .selected [data-name$=".py"]') 245 | selected = document.querySelectorAll('[is="tree-view-file"].selected span') 246 | if selected.length isnt 1 247 | alert('This command can only be run when exactly 1 file is selected') 248 | return 249 | fileName = selected[0].attributes["data-path"].value 250 | atom.workspace.open fileName 251 | .done (o) => 252 | @saveAndRunInRhino() 253 | -------------------------------------------------------------------------------- /lib/rhino-settings-view.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | class RhinoSettingsView 3 | constructor: (serializedState) -> 4 | # Create root element 5 | @element = document.createElement('div') 6 | @element.id = 'RhinoSettingsView' 7 | 8 | title = document.createElement('span') 9 | title.textContent = "Rhino Python Search Paths:" 10 | title.classList.add('text-highlight') 11 | title.classList.add('title') 12 | @element.appendChild(title) 13 | 14 | systemPathsNote = document.createElement('span') 15 | systemPathsNote.textContent = "(paths in blue are system paths and cannot be edited)" 16 | systemPathsNote.id = 'SystemPathsNote' 17 | @element.appendChild(systemPathsNote) 18 | 19 | ul = document.createElement('ul') 20 | li = document.createElement('li') 21 | li.setAttribute('v-repeat', 'paths') 22 | 23 | p = document.createElement('span') 24 | p.textContent = '{{path}}' 25 | p.setAttribute('v-on', 'click: select(this)') 26 | li.appendChild(p) 27 | 28 | li.setAttribute('class', "{{selected ? 'selected' : ''}}") 29 | li.classList.add("{{type}}") 30 | ul.appendChild(li) 31 | @element.appendChild(ul) 32 | 33 | btnGrpDiv = document.createElement('div') 34 | btnGrpDiv.classList.add('inline-block') 35 | btnGrpDiv.classList.add('btn-group') 36 | 37 | btn = document.createElement('button') 38 | btn.classList.add('btn') 39 | btn.classList.add('icon') 40 | btn.classList.add('icon-plus') 41 | btn.setAttribute('v-on', 'click: add') 42 | btnGrpDiv.appendChild(btn) 43 | 44 | btn = document.createElement('button') 45 | btn.classList.add('btn') 46 | btn.classList.add('icon') 47 | btn.classList.add('icon-dash') 48 | btn.setAttribute('v-attr', 'disabled: deleteDisabled') 49 | btn.setAttribute('v-on', 'click: delete') 50 | btnGrpDiv.appendChild(btn) 51 | 52 | btn = document.createElement('button') 53 | btn.classList.add('btn') 54 | btn.classList.add('icon') 55 | btn.classList.add('icon-arrow-up') 56 | btn.setAttribute('v-attr', 'disabled: upDisabled') 57 | btn.setAttribute('v-on', 'click: moveUp') 58 | btnGrpDiv.appendChild(btn) 59 | 60 | btn = document.createElement('button') 61 | btn.classList.add('btn') 62 | btn.classList.add('icon') 63 | btn.classList.add('icon-arrow-down') 64 | btn.setAttribute('v-attr', 'disabled: downDisabled') 65 | btn.setAttribute('v-on', 'click: moveDown') 66 | btnGrpDiv.appendChild(btn) 67 | 68 | # btn = document.createElement('button') 69 | # btn.classList.add('btn') 70 | # btn.classList.add('icon') 71 | # btn.classList.add('icon-file-directory') 72 | # btn.setAttribute('v-attr', 'disabled: showDisabled') 73 | # btn.setAttribute('v-on', 'click: show') 74 | # btnGrpDiv.appendChild(btn) 75 | 76 | btn = document.createElement('button') 77 | btn.classList.add('btn') 78 | btn.classList.add('icon') 79 | btn.classList.add('icon-sync') 80 | btn.setAttribute('v-attr', 'disabled: saveDisabled') 81 | btn.setAttribute('v-on', 'click: save') 82 | btn.textContent = 'save' 83 | btnGrpDiv.appendChild(btn) 84 | 85 | btn = document.createElement('button') 86 | btn.classList.add('btn') 87 | btn.classList.add('icon') 88 | btn.classList.add('icon-history') 89 | btn.setAttribute('v-attr', 'disabled: revertDisabled') 90 | btn.setAttribute('v-on', 'click: revert') 91 | btn.textContent = 'revert' 92 | btnGrpDiv.appendChild(btn) 93 | 94 | @element.appendChild(btnGrpDiv) 95 | 96 | # div = document.createElement('div') 97 | # div.id = 'restartScriptEngine' 98 | # cb = document.createElement('input') 99 | # cb.id = "restartScriptEngineCb" 100 | # cb.setAttribute('type', 'checkbox') 101 | # cb.setAttribute('v-model', 'restartEngineChecked') 102 | # cb.setAttribute('v-attr', 'disabled: saveDisabled') 103 | # #cb.setAttribute('v-on', 'click: restartEngineChecked = !restartEngineChecked') 104 | # lbl = document.createElement('label') 105 | # lbl.setAttribute('for', 'restartScriptEngineCb') 106 | # lbl.textContent = 'restart script engine on \'save\'' 107 | # div.appendChild(cb) 108 | # div.appendChild(lbl) 109 | # @element.appendChild(div) 110 | 111 | # Returns an object that can be retrieved when package is activated 112 | serialize: -> 113 | 114 | # Tear down any state and detach 115 | destroy: -> 116 | @element.remove() 117 | 118 | getElement: -> 119 | @element 120 | -------------------------------------------------------------------------------- /lib/talk-to-rhino.coffee: -------------------------------------------------------------------------------- 1 | $ = require 'jquery' 2 | _ = require 'underscore' 3 | 4 | module.exports = 5 | getCompletionData: (lines, callRhinoPosition, path, prefix, clearCache, cache, filter) -> 6 | return new Promise (resolve) -> 7 | suggestions = [] 8 | ccreq = JSON.stringify {Lines: lines, CaretColumn: callRhinoPosition.column, FileName: path} 9 | $.post "http://localhost:#{ atom.config.get 'rhino-python.httpPort'}/getcompletiondata", ccreq, 'json' 10 | .then (response) -> 11 | if /^no completion data/.test response 12 | console.log response 13 | else 14 | suggestions = ($.parseJSON response)?.map (cd) => 15 | {text: cd.Name, replacementPrefix: '', rightLabelHTML: '<- Rhino'} 16 | console.log "fetched from Rhino: #{suggestions?.length} suggestions", suggestions 17 | suggestions 18 | .always (newSuggestions) -> 19 | cache(callRhinoPosition, newSuggestions) 20 | resolve(if prefix then filter(prefix) else newSuggestions) 21 | .fail (response) -> 22 | console.log 'getCompletionData failed:', response 23 | 24 | getDocString: ({editor, bufferPosition}, lines) -> 25 | lines[lines.length-1] = lines[lines.length-1].replace /\($/, '' 26 | ccreq = JSON.stringify {Lines: lines, CaretColumn: bufferPosition.column, FileName: editor.getPath()} 27 | return $.post "http://localhost:#{ atom.config.get 'rhino-python.httpPort'}/getdocstring", ccreq, 'json' 28 | .then (response) -> 29 | if /^no completion data/.test response then response else ($.parseJSON response)?.ds 30 | 31 | getPythonSearchPaths: (callback) -> 32 | # I'm having problems consuming a Promise from a Vue (vue.js) so use a callback 33 | $.getJSON "http://localhost:#{atom.config.get 'rhino-python.httpPort'}/getpythonsearchpaths" 34 | .then (response) -> 35 | if /^no python search paths/.test response then response else response?.psp 36 | .done (psp) -> 37 | callback(_.map(psp, (p) => {path: p.path, selected: false, type: p.type})) 38 | 39 | setPythonSearchPaths: (paths, restartScriptEngine, callback) -> 40 | # I'm having problems consuming a Promise from a Vue (vue.js) so use a callback 41 | jsonPaths = JSON.stringify {Paths: _.map(_.filter(paths, (p) -> p.type == "user"), (p) -> p.path), RestartScriptEngine: restartScriptEngine} 42 | $.post "http://localhost:#{atom.config.get 'rhino-python.httpPort'}/setpythonsearchpaths", jsonPaths, 'json' 43 | .done (response) -> 44 | console.log 'setPythonSearchPaths:', response 45 | callback(response) 46 | 47 | rhinoIsListening: -> 48 | return $.getJSON "http://localhost:#{atom.config.get 'rhino-python.httpPort'}/ping" 49 | .then (response) -> 50 | if /Rhino(ceros|WIP|BETA| 7).app$/.test response.msg then [true, response.msg] else false 51 | 52 | runInRhino: (path) -> 53 | rpfreq = JSON.stringify {FileName: path} 54 | rhinoUrl = "http://localhost:#{ atom.config.get 'rhino-python.httpPort'}/runpythonscriptfile" 55 | $.post rhinoUrl, rpfreq, 'json' 56 | .then (response) -> 57 | ($.parseJSON response)?.msg 58 | -------------------------------------------------------------------------------- /menus/rhino-python.cson: -------------------------------------------------------------------------------- 1 | # See https://atom.io/docs/latest/creating-a-package#menus for more details 2 | 3 | # comment out the next 'menu' block and uncomment the other 'menu' block below to add a Rhino top menu 4 | # note: to toggle comments select some lines and press cmd-/ 5 | 6 | 'menu': [ 7 | { 8 | 'label': 'Packages' 9 | 'submenu': [ 10 | 'label': 'RhinoPython' 11 | 'submenu': [ 12 | {label: 'Save and Run in Rhino', command: 'rhino-python:saveAndRunInRhino'}, 13 | {label: 'Open autocomplete window', command: 'autocomplete-plus:activate'}, 14 | {label: 'Toggle Rhino Settings View', command: 'rhino-python:toggleRhinoSettingsView'} 15 | ] 16 | ] 17 | } 18 | ] 19 | 20 | # 'menu': [ 21 | # { 22 | # 'label': 'Rhino' 23 | # 'submenu': [ 24 | # {label: 'Save and Run in Rhino', command: 'rhino-python:saveAndRunInRhino'}, 25 | # {label: 'Open autocomplete window', command: 'autocomplete-plus:activate'} 26 | # ] 27 | # } 28 | # ] 29 | 30 | 'context-menu': 31 | 'atom-text-editor': [ 32 | { 33 | 'label': 'RhinoPython' 34 | 'submenu': [ 35 | {label: 'Save and Run in Rhino', command: 'rhino-python:saveAndRunInRhino'} 36 | ] 37 | } 38 | ] 39 | '[is="tree-view-file"] [data-name$=".py"]': [ 40 | { 41 | 'label': 'RhinoPython' 42 | 'submenu':[ 43 | {label: 'Save and Run in Rhino', command: 'rhino-python:saveAndRunInRhinoFromTreeView'} 44 | ] 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rhino-python", 3 | "version": "0.8.6", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "atom-message-panel": { 8 | "version": "1.2.0", 9 | "resolved": "https://registry.npmjs.org/atom-message-panel/-/atom-message-panel-1.2.0.tgz", 10 | "integrity": "sha1-ngj9iFPsnRWBr+IlMmFJGWhhGt8=", 11 | "requires": { 12 | "space-pen": "^4" 13 | } 14 | }, 15 | "balanced-match": { 16 | "version": "1.0.0", 17 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 18 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 19 | }, 20 | "brace-expansion": { 21 | "version": "1.1.11", 22 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 23 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 24 | "requires": { 25 | "balanced-match": "^1.0.0", 26 | "concat-map": "0.0.1" 27 | } 28 | }, 29 | "concat-map": { 30 | "version": "0.0.1", 31 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 32 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 33 | }, 34 | "d": { 35 | "version": "0.1.1", 36 | "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", 37 | "integrity": "sha1-2hhMU10Y2O57oqoim5FACfrhEwk=", 38 | "requires": { 39 | "es5-ext": "~0.10.2" 40 | } 41 | }, 42 | "emissary": { 43 | "version": "1.3.3", 44 | "resolved": "https://registry.npmjs.org/emissary/-/emissary-1.3.3.tgz", 45 | "integrity": "sha1-phjZLWgrIy0xER3DYlpd9mF5lgY=", 46 | "requires": { 47 | "es6-weak-map": "^0.1.2", 48 | "mixto": "1.x", 49 | "property-accessors": "^1.1", 50 | "underscore-plus": "1.x" 51 | } 52 | }, 53 | "es5-ext": { 54 | "version": "0.10.53", 55 | "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", 56 | "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", 57 | "requires": { 58 | "es6-iterator": "~2.0.3", 59 | "es6-symbol": "~3.1.3", 60 | "next-tick": "~1.0.0" 61 | }, 62 | "dependencies": { 63 | "d": { 64 | "version": "1.0.1", 65 | "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", 66 | "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", 67 | "requires": { 68 | "es5-ext": "^0.10.50", 69 | "type": "^1.0.1" 70 | } 71 | }, 72 | "es6-iterator": { 73 | "version": "2.0.3", 74 | "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", 75 | "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", 76 | "requires": { 77 | "d": "1", 78 | "es5-ext": "^0.10.35", 79 | "es6-symbol": "^3.1.1" 80 | } 81 | }, 82 | "es6-symbol": { 83 | "version": "3.1.3", 84 | "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", 85 | "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", 86 | "requires": { 87 | "d": "^1.0.1", 88 | "ext": "^1.1.2" 89 | } 90 | } 91 | } 92 | }, 93 | "es6-iterator": { 94 | "version": "0.1.3", 95 | "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz", 96 | "integrity": "sha1-1vWLjE/EE8JJtLqhl2j45NfIlE4=", 97 | "requires": { 98 | "d": "~0.1.1", 99 | "es5-ext": "~0.10.5", 100 | "es6-symbol": "~2.0.1" 101 | } 102 | }, 103 | "es6-symbol": { 104 | "version": "2.0.1", 105 | "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz", 106 | "integrity": "sha1-dhtcZ8/U8dGK+yNPaR1nhoLLO/M=", 107 | "requires": { 108 | "d": "~0.1.1", 109 | "es5-ext": "~0.10.5" 110 | } 111 | }, 112 | "es6-weak-map": { 113 | "version": "0.1.4", 114 | "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-0.1.4.tgz", 115 | "integrity": "sha1-cGzvnpmqI2undmwjnIueKG6n0ig=", 116 | "requires": { 117 | "d": "~0.1.1", 118 | "es5-ext": "~0.10.6", 119 | "es6-iterator": "~0.1.3", 120 | "es6-symbol": "~2.0.1" 121 | } 122 | }, 123 | "esprima": { 124 | "version": "1.0.4", 125 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", 126 | "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" 127 | }, 128 | "ext": { 129 | "version": "1.4.0", 130 | "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", 131 | "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", 132 | "requires": { 133 | "type": "^2.0.0" 134 | }, 135 | "dependencies": { 136 | "type": { 137 | "version": "2.1.0", 138 | "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", 139 | "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" 140 | } 141 | } 142 | }, 143 | "fs.realpath": { 144 | "version": "1.0.0", 145 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 146 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 147 | }, 148 | "function-bind": { 149 | "version": "1.1.1", 150 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 151 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 152 | }, 153 | "fuzzaldrin": { 154 | "version": "2.1.0", 155 | "resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz", 156 | "integrity": "sha1-kCBMPi/appQbso0WZF1BgGOpDps=" 157 | }, 158 | "glob": { 159 | "version": "7.1.6", 160 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 161 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 162 | "requires": { 163 | "fs.realpath": "^1.0.0", 164 | "inflight": "^1.0.4", 165 | "inherits": "2", 166 | "minimatch": "^3.0.4", 167 | "once": "^1.3.0", 168 | "path-is-absolute": "^1.0.0" 169 | } 170 | }, 171 | "grim": { 172 | "version": "1.5.0", 173 | "resolved": "https://registry.npmjs.org/grim/-/grim-1.5.0.tgz", 174 | "integrity": "sha1-sysI71Z88YUvgXWe2caLDXE5ajI=", 175 | "requires": { 176 | "emissary": "^1.2.0" 177 | } 178 | }, 179 | "has": { 180 | "version": "1.0.3", 181 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 182 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 183 | "requires": { 184 | "function-bind": "^1.1.1" 185 | } 186 | }, 187 | "hoister": { 188 | "version": "0.0.2", 189 | "resolved": "https://registry.npmjs.org/hoister/-/hoister-0.0.2.tgz", 190 | "integrity": "sha1-DQ2LHODxkVU+Ya/sZU9rGA65bl0=" 191 | }, 192 | "inflight": { 193 | "version": "1.0.6", 194 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 195 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 196 | "requires": { 197 | "once": "^1.3.0", 198 | "wrappy": "1" 199 | } 200 | }, 201 | "inherits": { 202 | "version": "2.0.4", 203 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 204 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 205 | }, 206 | "interpret": { 207 | "version": "1.4.0", 208 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", 209 | "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" 210 | }, 211 | "is-core-module": { 212 | "version": "2.1.0", 213 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz", 214 | "integrity": "sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==", 215 | "requires": { 216 | "has": "^1.0.3" 217 | } 218 | }, 219 | "jquery": { 220 | "version": "3.5.1", 221 | "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", 222 | "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==" 223 | }, 224 | "minimatch": { 225 | "version": "3.0.4", 226 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 227 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 228 | "requires": { 229 | "brace-expansion": "^1.1.7" 230 | } 231 | }, 232 | "mixto": { 233 | "version": "1.0.0", 234 | "resolved": "https://registry.npmjs.org/mixto/-/mixto-1.0.0.tgz", 235 | "integrity": "sha1-wyDvYbUvKJj1IuF9i7xtUG2EJbY=" 236 | }, 237 | "next-tick": { 238 | "version": "1.0.0", 239 | "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", 240 | "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" 241 | }, 242 | "notevil": { 243 | "version": "1.3.3", 244 | "resolved": "https://registry.npmjs.org/notevil/-/notevil-1.3.3.tgz", 245 | "integrity": "sha512-y4gR18Z2lIHeBREaZu788iii4/KLLe2jNPoZA8aEg4NWK1JwjmtjVyI3eypQKPEkOIPc++8C+byUUDc+SJDJgg==", 246 | "requires": { 247 | "esprima": "~1.0", 248 | "hoister": "~0.0" 249 | } 250 | }, 251 | "once": { 252 | "version": "1.4.0", 253 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 254 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 255 | "requires": { 256 | "wrappy": "1" 257 | } 258 | }, 259 | "path-is-absolute": { 260 | "version": "1.0.1", 261 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 262 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 263 | }, 264 | "path-parse": { 265 | "version": "1.0.6", 266 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 267 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" 268 | }, 269 | "property-accessors": { 270 | "version": "1.1.3", 271 | "resolved": "https://registry.npmjs.org/property-accessors/-/property-accessors-1.1.3.tgz", 272 | "integrity": "sha1-Hd6EAkYxhlkJ7zBwM2VoDF+SixU=", 273 | "requires": { 274 | "es6-weak-map": "^0.1.2", 275 | "mixto": "1.x" 276 | } 277 | }, 278 | "rechoir": { 279 | "version": "0.6.2", 280 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 281 | "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", 282 | "requires": { 283 | "resolve": "^1.1.6" 284 | } 285 | }, 286 | "resolve": { 287 | "version": "1.18.1", 288 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", 289 | "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", 290 | "requires": { 291 | "is-core-module": "^2.0.0", 292 | "path-parse": "^1.0.6" 293 | } 294 | }, 295 | "shelljs": { 296 | "version": "0.8.4", 297 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", 298 | "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", 299 | "requires": { 300 | "glob": "^7.0.0", 301 | "interpret": "^1.0.0", 302 | "rechoir": "^0.6.2" 303 | } 304 | }, 305 | "space-pen": { 306 | "version": "4.3.4", 307 | "resolved": "https://registry.npmjs.org/space-pen/-/space-pen-4.3.4.tgz", 308 | "integrity": "sha1-26uZvZZAr9h9ajFYJWTolOr8skE=", 309 | "requires": { 310 | "grim": "^1.0.0", 311 | "underscore-plus": "1.x" 312 | } 313 | }, 314 | "type": { 315 | "version": "1.2.0", 316 | "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", 317 | "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" 318 | }, 319 | "underscore": { 320 | "version": "1.7.0", 321 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", 322 | "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" 323 | }, 324 | "underscore-plus": { 325 | "version": "1.7.0", 326 | "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.7.0.tgz", 327 | "integrity": "sha512-A3BEzkeicFLnr+U/Q3EyWwJAQPbA19mtZZ4h+lLq3ttm9kn8WC4R3YpuJZEXmWdLjYP47Zc8aLZm9kwdv+zzvA==", 328 | "requires": { 329 | "underscore": "^1.9.1" 330 | }, 331 | "dependencies": { 332 | "underscore": { 333 | "version": "1.11.0", 334 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.11.0.tgz", 335 | "integrity": "sha512-xY96SsN3NA461qIRKZ/+qox37YXPtSBswMGfiNptr+wrt6ds4HaMw23TP612fEyGekRE6LNRiLYr/aqbHXNedw==" 336 | } 337 | } 338 | }, 339 | "vue": { 340 | "version": "git+https://git@github.com/vuejs/vue.git#b3c529992e322daa4c3a486137730078edd22ffd", 341 | "from": "git+https://git@github.com/vuejs/vue.git#b3c529992e322daa4c3a486137730078edd22ffd", 342 | "requires": { 343 | "notevil": "^1.0.0" 344 | } 345 | }, 346 | "wrappy": { 347 | "version": "1.0.2", 348 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 349 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 350 | } 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rhino-python", 3 | "main": "./lib/rhino-python", 4 | "version": "0.8.6", 5 | "description": "Editor support for writting python scripts that are executed by Rhinoceros (http://www.rhino3d.com).", 6 | "repository": "https://github.com/mcneel/rhino-python", 7 | "license": "MIT", 8 | "engines": { 9 | "atom": ">=0.185.0 <2.0.0" 10 | }, 11 | "dependencies": { 12 | "fuzzaldrin": "*", 13 | "shelljs": "*", 14 | "atom-message-panel": "1.2.0", 15 | "underscore": "1.7.0", 16 | "jquery": "*", 17 | "vue": "git+https://git@github.com/vuejs/vue.git#b3c529992e322daa4c3a486137730078edd22ffd" 18 | }, 19 | "providedServices": { 20 | "autocomplete.provider": { 21 | "versions": { 22 | "2.0.0": "provide" 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spec/rhino-autocomplete-plus-python-provider-spec.coffee: -------------------------------------------------------------------------------- 1 | {WorkspaceView} = require 'atom' 2 | RhinoProvider = require '../lib/rhino-autocomplete-plus-python-provider' 3 | 4 | describe "RhinoProvider", -> 5 | describe "when a string is passed to provider.getCallRhinoPosition", -> 6 | it "means that a position is returned if there is one otherwise null is returned", -> 7 | provider = new RhinoProvider() 8 | expect((provider.getCallRhinoPosition "import Rhi", 0)?.column).toBe(6) 9 | expect(provider.getCallRhinoPosition "importRhi", 0).toBe(null) 10 | expect((provider.getCallRhinoPosition "import Rhino.Geo", 0)?.column).toBe(12) 11 | expect((provider.getCallRhinoPosition "import Rhino.", 0)?.column).toBe(12) 12 | expect((provider.getCallRhinoPosition "import ", 0)?.column).toBe(6) 13 | expect(provider.getCallRhinoPosition "impor", 0).toBe(null) 14 | #ignore problems with double dots. Rhino will sort it out 15 | expect((provider.getCallRhinoPosition "import Rhino..", 0)?.column).toBe(13) 16 | -------------------------------------------------------------------------------- /spec/rhino-python-spec.coffee: -------------------------------------------------------------------------------- 1 | {WorkspaceView} = require 'atom' 2 | RhinoPython = require '../lib/rhino-python' 3 | 4 | # Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs. 5 | # 6 | # To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit` 7 | # or `fdescribe`). Remove the `f` to unfocus the block. 8 | 9 | describe "RhinoPython", -> 10 | activationPromise = null 11 | 12 | beforeEach -> 13 | atom.workspaceView = new WorkspaceView 14 | activationPromise = atom.packages.activatePackage('rhino-python') 15 | 16 | # describe "when the rhino-python:toggle event is triggered", -> 17 | # it "attaches and then detaches the view", -> 18 | # expect(atom.workspaceView.find('.rhino-python')).not.toExist() 19 | # 20 | # # This is an activation event, triggering it will cause the package to be 21 | # # activated. 22 | # atom.commands.dispatch atom.workspaceView.element, 'rhino-python:toggle' 23 | # 24 | # waitsForPromise -> 25 | # activationPromise 26 | # 27 | # runs -> 28 | # expect(atom.workspaceView.find('.rhino-python')).toExist() 29 | -------------------------------------------------------------------------------- /styles/rhino-python.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/stylesheets/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | .rhino-python { 8 | 9 | } 10 | 11 | // #RhinoSettingsView li { 12 | // list-style-type: none; 13 | // } 14 | #RhinoSettingsView span.title { 15 | margin: 5px; 16 | } 17 | 18 | #RhinoSettingsView ul li.system { 19 | color: #6494ed; 20 | } 21 | 22 | #restartScriptEngineCb { 23 | margin-left: 5px; 24 | margin-right: 5px; 25 | } 26 | --------------------------------------------------------------------------------