Skip to content
This repository has been archived by the owner on Jul 31, 2023. It is now read-only.

Remove solargraph dependency and update codeCompletion and intellisense options #309

Merged
merged 6 commits into from
Mar 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@
"vscode": "^1.1.4",
"vscode-debugadapter-testsupport": "^1.19.0"
},
"extensionDependencies": [
"castwide.solargraph"
],
"scripts": {
"vscode:prepublish": "tsc -p ./src",
"compile": "tsc -p ./src",
Expand Down Expand Up @@ -134,16 +131,16 @@
"isExecutable": true
},
"ruby.codeCompletion": {
"type": "string",
"enum": ["solargraph", "rcodetools", "none"],
"default": "solargraph",
"description": "Method to use for code completion."
"type": ["boolean", "string"],
"enum": [false, "rcodetools"],
"default": false,
"description": "Method to use for code completion. Use `false` to disable or if another extension provides this feature."
},
"ruby.intellisense": {
"type": "string",
"enum": ["solargraph", "rubyLocate", "none"],
"default": "solargraph",
"description": "Method to use for intellisense (go to definition, etc.)."
"type": ["boolean", "string"],
"enum": [false, "rubyLocate"],
"default": false,
"description": "Method to use for intellisense (go to definition, etc.). Use `false` to disable or if another extension provides this feature."
},
"ruby.useBundler": {
"type": "boolean",
Expand Down Expand Up @@ -289,7 +286,7 @@
"type": [ "boolean", "string" ],
"enum": [ false, "rubocop" ],
"default": false,
"description": "Which system to use for formatting, or false for no formatting"
"description": "Which system to use for formatting. Use `false` to disable or if another extension provides this feature."
}
}
},
Expand Down
30 changes: 15 additions & 15 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,33 +152,29 @@ Rufo is an alternative Ruby formatting tool. See the [VS Code Rufo Extension](ht

## Autocomplete

The `ruby.codeCompletion` setting lets you select a method for code completion and other intellisense features. Valid options are `solargraph`, `rcodetools`, and `none`.
The `ruby.codeCompletion` setting lets you select a method for code completion and other intellisense features. Valid options are `rcodetools` and `false`.

To enable method completion in Ruby, run `gem install solargraph` or `gem install rcodetools` based on the `ruby.codeCompletion` setting. You may need to restart Visual Studio Code the first time.
### rcodetools

To enable method completion in Ruby, run `gem install rcodetools`. You may need to restart Visual Studio Code the first time.

```ruby
[1, 2, 3].e #<= Press CTRL-Space here
```

For more information about using Solargraph, refer to the [Solargraph extension](https://marketplace.visualstudio.com/items?itemName=castwide.solargraph).

## Intellisense (Go to/Peek Definition)

Use the `ruby.intellisense` setting to select a `go to/peek definition` method. Valid options are `solargraph`, `rubyLocate`, and `none`.
### Solargraph

### Solargraph Intellisense
Solargraph is an alternative Ruby code completion tool. See the [Solargraph extension](https://marketplace.visualstudio.com/items?itemName=castwide.solargraph) if you want to try it.

Make sure the solargraph gem installed:
For more information about using Solargraph, refer to the [Solargraph extension](https://marketplace.visualstudio.com/items?itemName=castwide.solargraph).

```
gem install solargraph
```
## Intellisense (Go to/Peek Definition)

Solargraph's features now extend to providing go to/peek definition. See the [Solargraph extension](https://marketplace.visualstudio.com/items?itemName=castwide.solargraph) for more information.
Use the `ruby.intellisense` setting to select a `go to/peek definition` method. Valid options are `rubyLocate`, and `false`.

### RubyLocate Intellisense
### rubyLocate

Now includes workspace parsing functionality. Allows VS Code to `go to definition` and `peak definition` for modules, classes, and methods defined within the same workspace. You can set glob patterns to match including and excluding particular files. The exclude match also runs against directories on initial load, to reduce latency.
The `rubyLocate` option includes workspace parsing functionality. It allows VS Code to `go to definition` and `peak definition` for modules, classes, and methods defined within the same workspace. You can set glob patterns to match including and excluding particular files. The exclude match also runs against directories on initial load, to reduce latency.

The default settings are:

Expand All @@ -195,6 +191,10 @@ If you change these settings, currently you will need to reload your workspace.

We now provide go to definition within `erb` files, as well as syntax highlighting for `erb`.

### Solargraph

Solargraph now includes go to/peek definition and other language features. See the [Solargraph extension](https://marketplace.visualstudio.com/items?itemName=castwide.solargraph) for more information.

## TODO

- Unit/Integration tests debugging
Expand Down
232 changes: 121 additions & 111 deletions src/ruby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,9 @@ export function activate(context: ExtensionContext) {

registerHighlightProvider(context);
registerLinters(context);
if (vscode.workspace.getConfiguration('ruby').codeCompletion == 'rcodetools') {
registerCompletionProvider(context);
}
registerCompletionProvider(context);
registerFormatter(context);
if (vscode.workspace.getConfiguration('ruby').intellisense == 'rubyLocate') {
registerIntellisenseProvider(context);
}
registerIntellisenseProvider(context);
registerTaskProvider(context);
utils.loadEnv();
}
Expand Down Expand Up @@ -151,65 +147,67 @@ function registerLinters(ctx: ExtensionContext) {
}

function registerCompletionProvider(ctx: ExtensionContext) {
const completeCommand = function(args) {
let rctCompletePath = vscode.workspace.getConfiguration('ruby.rctComplete').get('commandPath', 'rct-complete');
args.push('--interpreter');
args.push(vscode.workspace.getConfiguration('ruby.interpreter').get('commandPath', 'ruby'));
if (process.platform === 'win32')
return cp.spawn('cmd', ['/c', rctCompletePath].concat(args));
return cp.spawn(rctCompletePath, args);
}
if (vscode.workspace.getConfiguration('ruby').codeCompletion == 'rcodetools') {
const completeCommand = function (args) {
let rctCompletePath = vscode.workspace.getConfiguration('ruby.rctComplete').get('commandPath', 'rct-complete');
args.push('--interpreter');
args.push(vscode.workspace.getConfiguration('ruby.interpreter').get('commandPath', 'ruby'));
if (process.platform === 'win32')
return cp.spawn('cmd', ['/c', rctCompletePath].concat(args));
return cp.spawn(rctCompletePath, args);
}

const completeTest = completeCommand(['--help']);
completeTest.on('exit', () => {
ctx.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
/** selector */'ruby',
/** provider */{
provideCompletionItems: function completionProvider(document, position, token) {
return new Promise((resolve, reject) => {
const line = position.line + 1;
const column = position.character;
let child = completeCommand([
'--completion-class-info',
'--dev',
'--fork',
'--line=' + line,
'--column=' + column
]);
let outbuf = [],
errbuf = [];
child.stderr.on('data', (data) => errbuf.push(data));
child.stdout.on('data', (data) => outbuf.push(data));
child.stdout.on('end', () => {
if (errbuf.length > 0) return reject(Buffer.concat(errbuf).toString());
let completionItems = [];
Buffer.concat(outbuf).toString().split('\n').forEach(function (elem) {
let items = elem.split('\t');
if (/^[^\w]/.test(items[0])) return;
if (items[0].trim().length === 0) return;
let completionItem = new vscode.CompletionItem(items[0]);
completionItem.detail = items[1];
completionItem.documentation = items[1];
completionItem.filterText = items[0];
completionItem.insertText = items[0];
completionItem.label = items[0];
completionItem.kind = vscode.CompletionItemKind.Method;
completionItems.push(completionItem);
}, this);
if (completionItems.length === 0)
return reject([]);
return resolve(completionItems);
const completeTest = completeCommand(['--help']);
completeTest.on('exit', () => {
ctx.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
/** selector */'ruby',
/** provider */{
provideCompletionItems: function completionProvider(document, position, token) {
return new Promise((resolve, reject) => {
const line = position.line + 1;
const column = position.character;
let child = completeCommand([
'--completion-class-info',
'--dev',
'--fork',
'--line=' + line,
'--column=' + column
]);
let outbuf = [],
errbuf = [];
child.stderr.on('data', (data) => errbuf.push(data));
child.stdout.on('data', (data) => outbuf.push(data));
child.stdout.on('end', () => {
if (errbuf.length > 0) return reject(Buffer.concat(errbuf).toString());
let completionItems = [];
Buffer.concat(outbuf).toString().split('\n').forEach(function (elem) {
let items = elem.split('\t');
if (/^[^\w]/.test(items[0])) return;
if (items[0].trim().length === 0) return;
let completionItem = new vscode.CompletionItem(items[0]);
completionItem.detail = items[1];
completionItem.documentation = items[1];
completionItem.filterText = items[0];
completionItem.insertText = items[0];
completionItem.label = items[0];
completionItem.kind = vscode.CompletionItemKind.Method;
completionItems.push(completionItem);
}, this);
if (completionItems.length === 0)
return reject([]);
return resolve(completionItems);
});
child.stdin.end(document.getText());
});
child.stdin.end(document.getText());
});
}
},
/** triggerCharacters */ ...['.']
}
},
/** triggerCharacters */ ...['.']
)
)
)
});
completeTest.on('error', () => 0);
});
completeTest.on('error', () => 0);
}
}

function registerFormatter(ctx: ExtensionContext) {
Expand All @@ -218,56 +216,68 @@ function registerFormatter(ctx: ExtensionContext) {

function registerIntellisenseProvider(ctx: ExtensionContext) {
// for locate: if it's a project, use the root, othewise, don't bother
if (vscode.workspace.rootPath) {
const refreshLocate = () => {
let progressOptions = { location: vscode.ProgressLocation.Window, title: 'Indexing Ruby source files' };
vscode.window.withProgress(progressOptions, () => locate.walk());
};
const settings: any = vscode.workspace.getConfiguration("ruby.locate") || {};
let locate = new Locate(vscode.workspace.rootPath, settings);
refreshLocate();
ctx.subscriptions.push(vscode.commands.registerCommand('ruby.reloadProject', refreshLocate));
if (vscode.workspace.getConfiguration('ruby').intellisense == 'rubyLocate') {
if (vscode.workspace.rootPath) {
const refreshLocate = () => {
let progressOptions = { location: vscode.ProgressLocation.Window, title: 'Indexing Ruby source files' };
vscode.window.withProgress(progressOptions, () => locate.walk());
};
const settings: any = vscode.workspace.getConfiguration("ruby.locate") || {};
let locate = new Locate(vscode.workspace.rootPath, settings);
refreshLocate();
ctx.subscriptions.push(vscode.commands.registerCommand('ruby.reloadProject', refreshLocate));

const watch = vscode.workspace.createFileSystemWatcher(settings.include);
watch.onDidChange(uri => locate.parse(uri.fsPath));
watch.onDidCreate(uri => locate.parse(uri.fsPath));
watch.onDidDelete(uri => locate.rm(uri.fsPath));
const locationConverter = match => new vscode.Location(vscode.Uri.file(match.file), new vscode.Position(match.line, match.char));
const defProvider = {
provideDefinition: (doc, pos) => {
const txt = doc.getText(doc.getWordRangeAtPosition(pos));
return locate.find(txt).then(matches => matches.map(locationConverter));
}
};
ctx.subscriptions.push(vscode.languages.registerDefinitionProvider(['ruby', 'erb'], defProvider));
const symbolKindTable = {
class: () => SymbolKind.Class,
module: () => SymbolKind.Module,
method: symbolInfo => symbolInfo.name === 'initialize' ? SymbolKind.Constructor : SymbolKind.Method,
classMethod: () => SymbolKind.Method,
};
const defaultSymbolKind = symbolInfo => {
console.warn(`Unknown symbol type: ${symbolInfo.type}`);
return SymbolKind.Variable;
};
// NOTE: Workaround for high CPU usage on IPC (channel.onread) when too many symbols returned.
// For channel.onread see issue like this: https://github.com/Microsoft/vscode/issues/6026
const numOfSymbolLimit = 3000;
const symbolsConverter = matches => matches.slice(0, numOfSymbolLimit).map(match => {
const symbolKind = (symbolKindTable[match.type] || defaultSymbolKind)(match);
return new SymbolInformation(match.name, symbolKind, match.containerName, locationConverter(match));
});
const docSymbolProvider = {
provideDocumentSymbols: (document, token) => {
return locate.listInFile(document.fileName).then(symbolsConverter);
}
};
ctx.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(['ruby', 'erb'], docSymbolProvider));
const workspaceSymbolProvider = {
provideWorkspaceSymbols: (query, token) => {
return locate.query(query).then(symbolsConverter);
}
const watch = vscode.workspace.createFileSystemWatcher(settings.include);
watch.onDidChange(uri => locate.parse(uri.fsPath));
watch.onDidCreate(uri => locate.parse(uri.fsPath));
watch.onDidDelete(uri => locate.rm(uri.fsPath));
const locationConverter = match => new vscode.Location(vscode.Uri.file(match.file), new vscode.Position(match.line, match.char));
const defProvider = {
provideDefinition: (doc, pos) => {
const txt = doc.getText(doc.getWordRangeAtPosition(pos));
return locate.find(txt).then(matches => matches.map(locationConverter));
}
};
ctx.subscriptions.push(vscode.languages.registerDefinitionProvider(['ruby', 'erb'], defProvider));
const symbolKindTable = {
class: () => SymbolKind.Class,
module: () => SymbolKind.Module,
method: symbolInfo => symbolInfo.name === 'initialize' ? SymbolKind.Constructor : SymbolKind.Method,
classMethod: () => SymbolKind.Method,
};
const defaultSymbolKind = symbolInfo => {
console.warn(`Unknown symbol type: ${symbolInfo.type}`);
return SymbolKind.Variable;
};
// NOTE: Workaround for high CPU usage on IPC (channel.onread) when too many symbols returned.
// For channel.onread see issue like this: https://github.com/Microsoft/vscode/issues/6026
const numOfSymbolLimit = 3000;
const symbolsConverter = matches => matches.slice(0, numOfSymbolLimit).map(match => {
const symbolKind = (symbolKindTable[match.type] || defaultSymbolKind)(match);
return new SymbolInformation(match.name, symbolKind, match.containerName, locationConverter(match));
});
const docSymbolProvider = {
provideDocumentSymbols: (document, token) => {
return locate.listInFile(document.fileName).then(symbolsConverter);
}
};
ctx.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(['ruby', 'erb'], docSymbolProvider));
const workspaceSymbolProvider = {
provideWorkspaceSymbols: (query, token) => {
return locate.query(query).then(symbolsConverter);
}
};
ctx.subscriptions.push(vscode.languages.registerWorkspaceSymbolProvider(workspaceSymbolProvider));
} else {
var rubyLocateUnavailable = () => {
vscode.window.showInformationMessage('There is not an open workspace for rubyLocate to reload.');
};
ctx.subscriptions.push(vscode.commands.registerCommand('ruby.reloadProject', rubyLocateUnavailable));
}
} else {
var rubyLocateDisabled = () => {
vscode.window.showInformationMessage('The `ruby.intellisense` configuration is not set to use rubyLocate.')
};
ctx.subscriptions.push(vscode.languages.registerWorkspaceSymbolProvider(workspaceSymbolProvider));
ctx.subscriptions.push(vscode.commands.registerCommand('ruby.reloadProject', rubyLocateDisabled));
}
}