Skip to content
This repository was archived by the owner on Jul 2, 2023. It is now read-only.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Erlang/OTP Support for Visual Studio Code

This extension provides Erlang/OTP support for [Visual Studio Code](https://code.visualstudio.com/)and is available at the [Marketplace](https://marketplace.visualstudio.com/items?itemName=yuce.erlang-otp).
This experimantal extension provides Erlang/OTP support for [Visual Studio Code](https://code.visualstudio.com/).

## News

Expand All @@ -19,7 +19,6 @@ setting `erlang.enableExperimentalAutoComplete` to `true` in your user settings
## Planned Features

* Build support
* Erlang shell

## Work In Progress

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "erlang-otp",
"displayName": "Erlang/OTP",
"description": "Erlang/OTP support with syntax highlighting, auto-indent and snippets",
"version": "0.2.1",
"version": "0.2.2",
"author": {
"name": "Yuce Tekol"
},
Expand Down Expand Up @@ -59,6 +59,9 @@
"typescript": "^1.7.5",
"vscode": "^0.11.0"
},
"dependencies": {
"whatels": "^0.2.2"
},
"repository": {
"type": "git",
"url": "https://github.com/yuce/erlang-vscode"
Expand Down
192 changes: 143 additions & 49 deletions src/completion_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,39 @@

import {CompletionItemProvider, TextDocument, Position, CancellationToken,
CompletionItem, CompletionItemKind} from 'vscode';

let fs = require('fs');
import {Symbols, FunctionInfo, CallbackAction} from 'whatels';
import {WhatelsClient} from './whatels_client';
import fs = require('fs');
import path = require('path');

const RE_MODULE = /(\w+):$/;

interface FunctionCompletionData {
name: string;
// detail: string;
}

export class ErlangCompletionProvider implements CompletionItemProvider {
private modules:any = null;
private stdModules: any = null;
private moduleNames: string[] = null;
private docPath: string;
private genericCompletionItems: CompletionItem[] = null;
private moduleCompletionItems: CompletionItem[] = null;
private stdLibCompletionItems: CompletionItem[] = null;
private workspaceCompletionItems: CompletionItem[] = null;

constructor(private completionPath: string) {}
constructor(private whatelsClient: WhatelsClient,
private completionPath: string)
{
whatelsClient.subscribe((action, msg) => {
if (action == CallbackAction.getSymbols) {
this.genericCompletionItems = null;
if (msg.path == this.docPath) {
// invalidate completion items of the current doc
this.docPath = '';
this.moduleCompletionItems = null;
}
}
else if (action == CallbackAction.discardPath) {
this.genericCompletionItems = null;
}
});
}

public provideCompletionItems(doc: TextDocument,
pos: Position,
Expand All @@ -27,67 +44,144 @@ export class ErlangCompletionProvider implements CompletionItemProvider {
return new Promise<CompletionItem[]>((resolve, reject) => {
const line = doc.lineAt(pos.line);
const m = RE_MODULE.exec(line.text.substring(0, pos.character));
if (this.modules === null) {
this.readCompletionJson(this.completionPath, modules => {
this.modules = modules;
(m === null)?
this.resolveModuleNames(resolve)
: this.resolveFunNames(m[1], resolve);
});
if (m === null) {
this.resolveGenericItems(resolve, reject, doc.fileName);
}
else {
(m === null)?
this.resolveModuleNames(resolve)
: this.resolveFunNames(m[1], resolve);
resolve([]);
// const moduleName = m[1];
// this.whatelsClient.getAllPathSymbols().then(
// pathSymbols => this.resolveModuleItems(resolve, moduleName, pathSymbols),
// err => reject(err)
// );
// if (this.stdModules === null) {
// this.readCompletionJson(this.completionPath, modules => {
// this.stdModules = modules;
// this.resolveFunNames(m[1], resolve);
// });
// }
// else {
// this.resolveFunNames(m[1], resolve);
// }
}
});
}

private resolveFunNames(module, resolve) {
resolve(this.makeModuleFunsCompletion(module));
private resolveGenericItems(resolve, reject, path: string) {
this.getGenericCompletionItems(path).then(
items => resolve(items),
err => reject(err)
)
}

private resolveModuleNames(resolve) {
if (!this.genericCompletionItems) {
this.genericCompletionItems = this.makeGenericCompletion();
}
resolve(this.genericCompletionItems);
private getGenericCompletionItems(path: string): Thenable<CompletionItem[]> {
return new Promise<CompletionItem[]>((resolve, reject) => {
if (this.genericCompletionItems) {
resolve(this.genericCompletionItems);
}
else {
let cis: CompletionItem[] = [];
Promise.all([this.getModuleCompletionItems(path),
this.getWorkspaceCompletionItems(),
this.getStdLibCompletionItems()]).then(
allCompletionItems => {
allCompletionItems.forEach(items => {
items.forEach(ci => cis.push(ci));
});
resolve(cis);
},
err => reject(err)
);
this.genericCompletionItems = cis;
}
});
}

private makeFunctionCompletionItem(name: string): CompletionItem {
const item = new CompletionItem(name);
// item.documentation = cd.detail;
item.kind = CompletionItemKind.Function;
return item;
private getModuleCompletionItems(path: string): Thenable<CompletionItem[]> {
return new Promise<CompletionItem[]>((resolve, reject) => {
if (this.moduleCompletionItems && path == this.docPath) {
resolve(this.moduleCompletionItems);
}
else {
this.whatelsClient.getPathSymbols(path).then(
symbols => {
this.docPath = path;
resolve(this.createModuleCompletionItems(path, symbols));
},
err => reject(err)
);
}
});
}

private makeModuleNameCompletionItem(name: string): CompletionItem {
const item = new CompletionItem(name);
item.kind = CompletionItemKind.Module;
return item;
private getStdLibCompletionItems(): Thenable<CompletionItem[]> {
return new Promise<CompletionItem[]>((resolve, reject) => {
if (this.stdLibCompletionItems) {
resolve(this.stdLibCompletionItems);
}
else {
this.readCompletionJson(this.completionPath, modules => {
this.stdModules = modules;
resolve(this.createStdLibCompletionItems(modules));
});
}
});
}

private makeModuleFunsCompletion(module: string): CompletionItem[] {
const moduleFuns = this.modules[module] || [];
return moduleFuns.map(name => {
return this.makeFunctionCompletionItem(name);
private getWorkspaceCompletionItems(): Thenable<CompletionItem[]> {
return new Promise<CompletionItem[]>((resolve, reject) => {
if (this.workspaceCompletionItems) {
resolve(this.workspaceCompletionItems);
}
else {
this.whatelsClient.getAllPathSymbols().then(
pathSymbols => resolve(this.createWorkspaceCompletionItems(pathSymbols)),
err => reject(err)
);
}
});
}

private makeGenericCompletion(): CompletionItem[] {
const modules = this.modules || {};
const names = [];
for (let k in modules) {
names.push(k);
private createModuleCompletionItems(path: string, symbols: Symbols) {
let cis: CompletionItem[] = [];
if (symbols && symbols.functions) {
let funNames = new Set(symbols.functions.map(f => {
return f.name;
}));
funNames.forEach(name => {
var item = new CompletionItem(name);
item.kind = CompletionItemKind.Function;
cis.push(item);
});
}
names.sort();
return names.map(name => {
return this.makeModuleNameCompletionItem(name);
});
return this.moduleCompletionItems = cis;
}

private createStdLibCompletionItems(modules) {
let cis: CompletionItem[] = [];
for (var k in modules) {
var item = new CompletionItem(k);
item.kind = CompletionItemKind.Module;
cis.push(item);
}
return this.stdLibCompletionItems = cis;
}

private createWorkspaceCompletionItems(pathSymbols) {
let cis: CompletionItem[] = [];
for (var p in pathSymbols) {
var item = new CompletionItem(path.basename(p, '.erl'));
item.kind = CompletionItemKind.Module;
cis.push(item);
}
console.log('createWorkspaceCompletionItems');
console.log(cis);
console.log(this);
return this.workspaceCompletionItems = cis;
}

private readCompletionJson(filename: string, done: Function): any {
fs.readFile(filename, (err, data) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) {
console.log(`Cannot read: ${filename}`);
done({});
Expand Down
22 changes: 19 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
import {ExtensionContext, Disposable, workspace, window, languages,
Hover} from 'vscode';
import {ErlangCompletionProvider} from './completion_provider';
// import {range, debounce} from 'lodash';
import {ErlangDocumentSymbolProvider, ErlangWorkspaceDocumentSymbolProvider} from './symbol_provider';
import {WhatelsClient} from './whatels_client';


export function activate(ctx: ExtensionContext) {
languages.setLanguageConfiguration('erlang', {
Expand Down Expand Up @@ -64,13 +66,27 @@ export function activate(ctx: ExtensionContext) {
// enable auto completion
let config = workspace.getConfiguration('erlang');
if (config['enableExperimentalAutoComplete']) {
let completionJsonPath = ctx.asAbsolutePath("./priv/erlang-libs.json");
const whatelsClient = createWhatelsClient(workspace.rootPath);
ctx.subscriptions.push(whatelsClient);
const completionJsonPath = ctx.asAbsolutePath("./priv/erlang-libs.json");
ctx.subscriptions.push(languages.registerCompletionItemProvider({
language: 'erlang'
}, new ErlangCompletionProvider(completionJsonPath), ':'));
}, new ErlangCompletionProvider(whatelsClient, completionJsonPath), ':'));
ctx.subscriptions.push(languages.registerDocumentSymbolProvider({
language: 'erlang'
}, new ErlangDocumentSymbolProvider(whatelsClient)));
ctx.subscriptions.push(languages.registerWorkspaceSymbolProvider(
new ErlangWorkspaceDocumentSymbolProvider(whatelsClient)
));
}
}

export function deactivate() {
}

function createWhatelsClient(rootPath: string) {
const wc = new WhatelsClient();
wc.watch(`${rootPath}/src/*.erl`);
wc.watch(`${rootPath}/apps/**/src/*.erl`);
return wc;
}
Loading