├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── lib ├── autocomplete-ruby-client.coffee ├── autocomplete-ruby-provider.coffee ├── autocomplete-ruby.coffee └── gem-home.coffee ├── package.json └── spec └── autocomplete-ruby-spec.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .c9 3 | .tags 4 | npm-debug.log 5 | node_modules 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianhattendorf/autocomplete-ruby/7cc0b8c1e3a82dda56f2ec434c281bfbc369f55d/CHANGELOG.md -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Ian Hattendorf 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 | # Atom Autocomplete+ Ruby Suggestions 2 | [![Dependency status](https://david-dm.org/ianhattendorf/autocomplete-ruby.svg)](https://david-dm.org/ianhattendorf/autocomplete-ruby) 3 | [![GitHub version](https://badge.fury.io/gh/ianhattendorf%2Fautocomplete-ruby.svg)](http://badge.fury.io/gh/ianhattendorf%2Fautocomplete-ruby) 4 | 5 | Provides intelligent code completion for Ruby. Requires [RSense](https://github.com/rsense/rsense) and [Autocomplete+](https://github.com/atom-community/autocomplete-plus). 6 | 7 | ## :no_entry: [UNMAINTAINED] 8 | [RSense](https://github.com/rsense/rsense) hasn't been actively developed since late 2014 leaving many unresolved bugs. 9 | 10 | For a possible alternative, see [atom-solargraph](https://github.com/castwide/atom-solargraph), based on [solargraph](https://github.com/castwide/solargraph), which is currently (April 2018) seeing active development. 11 | 12 | ## Status 13 | Works for the most part, however **not on Windows**. This is due to a bug in [RSense](https://github.com/rsense/rsense), which is no longer being developed. Any bugs/issues related to rsense itself will need to be reported in that repo, and probably won't be fixed. 14 | 15 | Please read the [Known Issues](#known-issues) before reporting any issues. 16 | 17 | ## Why? 18 | Because I wanted Ruby code completion in Atom and [atom-rsense](https://github.com/rsense/atom-rsense) is broken and hasn't been updated since June 2014. 19 | 20 | ## Installation 21 | Make sure you have Java installed on your machine. 22 | 23 | Install rsense: 24 | ```shell 25 | $ gem install rsense 26 | ``` 27 | 28 | If you get an error about not being able to find `rsense` after opening a ruby file, you will need to set the path to the rsense binary in the plugin settings. The path is different depending on which OS/Ruby environment manager you are using. Executing `which rsense` or `gem environment` might help you locate it. 29 | 30 | ## Usage 31 | Just type some stuff, and autocomplete+ will automatically show you some suggestions. 32 | 33 | Note: If you use Winows, it might take about 10 seconds after the first suggestions pop up before rsense will give you any suggestions. 34 | 35 | ## Known Issues 36 | - Sometimes the environment isn't configured correctly when launching Atom from it's launcher (any custom config in `.bashrc`, `.bash_profile`, etc.). If you have any issues with the plugin, please try launching atom from the terminal first: `$ atom`. 37 | - RSense doesn't appear to work on Windows. 38 | 39 | Feel free to report any other issues you encounter. 40 | 41 | ## Development 42 | Clone the repository into your working directory: 43 | ```shell 44 | $ git clone git@github.com:ianhattendorf/autocomplete-ruby.git 45 | ``` 46 | 47 | Install dependencies: 48 | ```shell 49 | $ cd autocomplete-ruby 50 | $ apm install 51 | ``` 52 | 53 | Link to Atom as a dev package: 54 | ```shell 55 | $ apm link --dev 56 | ``` 57 | 58 | Feel free to fork it and submit a pull request for any changes you make. 59 | -------------------------------------------------------------------------------- /lib/autocomplete-ruby-client.coffee: -------------------------------------------------------------------------------- 1 | $ = require('jquery') 2 | TableParser = require('table-parser') 3 | exec = require('child_process').exec 4 | os = require('os') 5 | String.prototype.replaceAll = (s, r) -> @split(s).join(r) 6 | 7 | module.exports = 8 | class RsenseClient 9 | constructor: -> 10 | @projectPath = atom.project.getPaths()[0] 11 | @projectPath = '.' unless @projectPath 12 | @rsensePath = atom.config.get('autocomplete-ruby.rsensePath') 13 | @port = atom.config.get('autocomplete-ruby.port') 14 | @serverUrl = "http://localhost:#{@port}" 15 | @rsenseStarted = false 16 | @rsenseProcess = null 17 | 18 | # Check if an rsense server is already running. 19 | # This can detect all rsense processes even those without pid files. 20 | startRsenseUnix: => 21 | start = @startRsenseCommand 22 | 23 | exec("ps -ef | head -1; ps -ef | grep java", 24 | (error, stdout, stderr) -> 25 | if error != null 26 | atom.notifications.addError('Error looking for resense process', 27 | {detail: "autocomplete-ruby: exec error: #{error}", dismissable: true} 28 | ) 29 | else 30 | @rsenseProcess = $.grep(TableParser.parse(stdout), (process) -> 31 | process.CMD.join(' ').match( /rsense.*--port.*--path/ ) 32 | )[0] 33 | if @rsenseProcess == undefined || @rsenseProcess == null 34 | start() 35 | else 36 | @rsenseStarted = true 37 | ) 38 | 39 | # Before trying to start in Windows we need to kill any existing rsense servers, so 40 | # as to not end up with multiple rsense servsers unkillable by 'rsense stop' 41 | # This means that running two atoms and closing one, kills rsense for the other 42 | startRsenseWin32: => 43 | return if @rsenseStarted 44 | start = @startRsenseCommand 45 | 46 | exec("#{@rsensePath} stop", 47 | (error, stdout, stderr) => 48 | if error == null 49 | start() 50 | else 51 | atom.notifications.addError('Error stopping rsense', 52 | {detail: "autocomplete-ruby: exec error: #{error}", dismissable: true} 53 | ) 54 | @rsenseStarted = false 55 | ) 56 | 57 | startRsenseCommand: => 58 | return if @rsenseStarted 59 | exec("#{@rsensePath} start --port #{@port} --path #{@projectPath}", 60 | (error, stdout, stderr) -> 61 | if error != null 62 | atom.notifications.addError('Error starting rsense', 63 | {detail: "autocomplete-ruby: exec error: #{error}#{os.EOL}" + 64 | "(You might need to set the rsense path, see the readme)", 65 | dismissable: true} 66 | ) 67 | else 68 | @rsenseStarted = true 69 | ) 70 | 71 | # First count how many atom windows are open. 72 | # If there is only one open, then kill the rsense process. 73 | # This is also able to kill an rsense process without a pid file. 74 | # Otherwise do nothing so you will still be able to use rsense in other windows. 75 | stopRsenseUnix: => 76 | stopCommand = @stopRsense 77 | 78 | exec("ps -ef | head -1; ps -ef | grep atom", 79 | (error, stdout, stderr) -> 80 | if error != null 81 | atom.notifications.addError('Error looking for atom process', 82 | {detail: "autocomplete-ruby: exec error: #{error}", dismissable: true} 83 | ) 84 | else 85 | @atomProcesses = $.grep(TableParser.parse(stdout), (process) -> 86 | process.CMD.join(' ').match( /--type=renderer.*--node-integration=true/ ) 87 | ) 88 | if @atomProcesses.length < 2 89 | try 90 | process.kill(@rsenseProcess.PID[0], 'SIGKILL') if @rsenseProcess 91 | catch ex 92 | # ignore error if kill fails 93 | console.debug "exception killing process: #{ex}" 94 | stopCommand() 95 | ) 96 | 97 | stopRsense: => 98 | return if !@rsenseStarted 99 | exec("#{@rsensePath} stop", 100 | (error, stdout, stderr) -> 101 | if error != null 102 | atom.notifications.addError('Error stopping rsense', 103 | {detail: "autocomplete-ruby: exec error: #{error}", dismissable: true} 104 | ) 105 | else 106 | @rsenseStarted = false 107 | ) 108 | 109 | checkCompletion: (editor, buffer, row, column, callback) => 110 | code = buffer.getText().replaceAll('\n', '\n'). 111 | replaceAll('%', '%25') 112 | 113 | request = 114 | command: 'code_completion' 115 | project: @projectPath 116 | file: editor.getPath() 117 | code: code 118 | location: 119 | row: row 120 | column: column 121 | 122 | $.ajax @serverUrl, 123 | type: 'POST' 124 | dataType: 'json' 125 | contentType: 'application/json', 126 | data: JSON.stringify request 127 | error: (jqXHR, textStatus, errorThrown) -> 128 | # send empty array to callback 129 | # to avoid autocomplete-plus brick 130 | callback [] 131 | console.error textStatus 132 | success: (data, textStatus, jqXHR) -> 133 | callback data.completions 134 | 135 | return [] 136 | -------------------------------------------------------------------------------- /lib/autocomplete-ruby-provider.coffee: -------------------------------------------------------------------------------- 1 | RsenseClient = require './autocomplete-ruby-client.coffee' 2 | IS_WIN32 = process.platform == 'win32' 3 | 4 | String.prototype.regExpEscape = () -> 5 | return @replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") 6 | 7 | module.exports = 8 | class RsenseProvider 9 | selector: '.source.ruby' 10 | disableForSelector: '.source.ruby .comment' 11 | @suggestionPriority = atom.config.get('autocomplete-ruby.suggestionPriority') 12 | 13 | inclusionPriority: 1 14 | suggestionPriority: 2 if @suggestionPriority == true 15 | 16 | rsenseClient: null 17 | 18 | constructor: -> 19 | @rsenseClient = new RsenseClient() 20 | @rsenseClient.startRsenseUnix() if !IS_WIN32 21 | @lastSuggestions = [] 22 | 23 | getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) -> 24 | @rsenseClient.startRsenseWin32() if IS_WIN32 25 | new Promise (resolve) => 26 | # rsense expects 1-based positions 27 | row = bufferPosition.row + 1 28 | col = bufferPosition.column + 1 29 | completions = @rsenseClient.checkCompletion(editor, 30 | editor.buffer, row, col, (completions) => 31 | suggestions = @findSuggestions(prefix, completions) 32 | if(suggestions?.length) 33 | @lastSuggestions = suggestions 34 | 35 | # request completion on `.` and `::` 36 | resolve(@lastSuggestions) if prefix == '.' || prefix == '::' 37 | 38 | resolve(@filterSuggestions(prefix, @lastSuggestions)) 39 | ) 40 | 41 | findSuggestions: (prefix, completions) -> 42 | if completions? 43 | suggestions = [] 44 | for completion in completions 45 | kind = completion.kind.toLowerCase() 46 | kind = "import" if kind == "module" 47 | suggestion = 48 | text: completion.name 49 | type: kind 50 | leftLabel: completion.base_name 51 | suggestions.push(suggestion) 52 | suggestions.sort (x, y) -> 53 | if x.text>y.text 54 | 1 55 | else if x.text 65 | suggestionBuffer = [] 66 | 67 | if(!prefix?.length || !suggestions?.length) 68 | return [] 69 | 70 | expression = new RegExp("^"+prefix.regExpEscape(), "i") 71 | 72 | for suggestion in suggestions 73 | if expression.test(suggestion.text) 74 | suggestion.replacementPrefix = prefix 75 | suggestionBuffer.push(suggestion) 76 | 77 | return suggestionBuffer 78 | 79 | dispose: -> 80 | return @rsenseClient.stopRsense() if IS_WIN32 81 | @rsenseClient.stopRsenseUnix() 82 | -------------------------------------------------------------------------------- /lib/autocomplete-ruby.coffee: -------------------------------------------------------------------------------- 1 | RsenseProvider = require './autocomplete-ruby-provider.coffee' 2 | GEM_HOME = require('./gem-home.coffee') 3 | 4 | module.exports = 5 | config: 6 | rsensePath: 7 | description: 'The location of the rsense executable' 8 | type: 'string' 9 | default: "#{GEM_HOME}/rsense" 10 | port: 11 | description: 'The port the rsense server is running on' 12 | type: 'integer' 13 | default: 47367 14 | minimum: 1024 15 | maximum: 65535 16 | suggestionPriority: 17 | description: 'Show autocomplete-ruby content before default autocomplete-plus provider' 18 | default: false 19 | type: 'boolean' 20 | 21 | rsenseProvider: null 22 | 23 | activate: (state) -> 24 | @rsenseProvider = new RsenseProvider() 25 | 26 | provideAutocompletion: -> 27 | @rsenseProvider 28 | 29 | deactivate: -> 30 | @rsenseProvider?.dispose() 31 | @rsenseProvider = null 32 | -------------------------------------------------------------------------------- /lib/gem-home.coffee: -------------------------------------------------------------------------------- 1 | execSync = require('child_process').execSync 2 | 3 | platformHome = process.env[if process.platform is 'win32' then 'USERPROFILE' else 'HOME'] 4 | 5 | getExecPathFromGemEnv = -> 6 | stdout = execSync 'gem environment' 7 | 8 | line = stdout.toString().split(/\r?\n/) 9 | .find((l) -> ~l.indexOf('EXECUTABLE DIRECTORY')) 10 | if line 11 | line[line.indexOf(': ') + 2..] 12 | else 13 | undefined 14 | 15 | gemHome = -> 16 | if process.env.GEM_HOME 17 | "#{process.env.GEM_HOME}/bin" 18 | else 19 | getExecPathFromGemEnv() ? "#{platformHome}/.gem/ruby/2.3.0" 20 | 21 | module.exports = gemHome() 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "autocomplete-ruby", 3 | "main": "./lib/autocomplete-ruby", 4 | "version": "0.2.8", 5 | "author": { 6 | "name": "Ian Hattendorf", 7 | "email": "ianhattendorf@gmail.com", 8 | "url": "http://ianhattendorf.com/" 9 | }, 10 | "description": "Intelligent Ruby code completion", 11 | "keywords": [ 12 | "autocomplete", 13 | "autocomplete-plus", 14 | "rsense", 15 | "ruby" 16 | ], 17 | "repository": "https://github.com/ianhattendorf/autocomplete-ruby", 18 | "homepage": "https://github.com/ianhattendorf/autocomplete-ruby", 19 | "bugs": { 20 | "url": "https://github.com/ianhattendorf/autocomplete-ruby/issues" 21 | }, 22 | "license": "MIT", 23 | "engines": { 24 | "atom": ">=0.174.0 <2.0.0" 25 | }, 26 | "dependencies": { 27 | "jquery": "^3", 28 | "table-parser": "^0.1.3" 29 | }, 30 | "providedServices": { 31 | "autocomplete.provider": { 32 | "description": "Intelligent Ruby code completion", 33 | "versions": { 34 | "2.0.0": "provideAutocompletion" 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spec/autocomplete-ruby-spec.coffee: -------------------------------------------------------------------------------- 1 | AutocompleteRuby = require '../lib/autocomplete-ruby' 2 | 3 | describe "AutocompleteRuby", -> 4 | [workspaceElement, activationPromise] = [] 5 | 6 | beforeEach -> 7 | workspaceElement = atom.views.getView(atom.workspace) 8 | activationPromise = atom.packages.activatePackage('autocomplete-ruby') 9 | waitsForPromise -> activationPromise 10 | 11 | describe "autocomplete-ruby", -> 12 | it 'Starts and stops rsense', -> 13 | rsenseProvider = AutocompleteRuby.rsenseProvider 14 | rsenseClient = rsenseProvider.rsenseClient 15 | 16 | expect(rsenseClient.rsenseStarted).toBe(false) 17 | 18 | # The first request for autocompletion starts rsense 19 | rsenseProvider.requestHandler() 20 | expect(rsenseClient.rsenseStarted).toBe(true) 21 | 22 | rsenseClient.stopRsense() 23 | expect(rsenseClient.rsenseStarted).toBe(true) 24 | --------------------------------------------------------------------------------