akurath
Version:
IDE Frontend for codepsaces.io
169 lines (138 loc) • 4.48 kB
JavaScript
var Q = require('q');
var _ = require('lodash');
var path = require('path');
var utils = require('../utils');
function CodeComplete(events, workspace, project) {
this.events = events;
this.workspace = workspace;
this.project = project;
this.handlers = [];
_.bindAll(this);
};
/*
* Add a handler for codecompletion
* The 'handler' function will be called with an option object
* and should return a list of tag with the format:
* {
* 'name': "Tag name",
* 'file': "path relative to the workspace root",
* 'score': 1, // It will default to 1
* 'meta': "meta information: ex: 'class', 'function'",
* 'content': "Description html content"
* }
*/
CodeComplete.prototype.addHandler = function(name, handler) {
var _handler = _.find(this.handlers, function(h) {
return h.name == name;
})
if (_handler) {
return Q.reject(new Error("Handler already exists"));
}
this.handlers.push({
'name': name,
'handler': handler
});
return Q();
}
/*
* Add a tags indexer for codecompletion
* The populate will be called when necessaray to update a tags idnex used
* to return codecompletion results
*/
CodeComplete.prototype.addIndex = function(name, populate, options) {
var that = this;
var index = null;
var inProgress = null;
options = _.defaults({}, options || {}, {
'interval': 60*1000 //1min
})
var populateIndex = function() {
if (inProgress) return inProgress;
inProgress = Q(populate({
'root': that.workspace.root,
'project': that.project
})).then(function(items) {
index = items;
}, function(err) {
index = null;
return Q.reject(err);
}).fin(function() {
inProgress = null;
});
return inProgress;
};
// Populate the index when there are changes
var throttled = _.throttle(populateIndex, options.interval);
this.events.on("watch.change.update", throttled);
this.events.on("watch.change.create", throttled);
this.events.on("watch.change.delete", throttled);
// Add namespace
this.addHandler(name, function(options) {
var prepare = Q();
// if no index yet: populate the index
if (!index) {
prepare = populateIndex();
}
// If processing new data, wait for new update
if (inProgress) prepare = inProgress;
// Filter the index for getting the results
return prepare.then(function() {
// Filter is done by the 'get'
return index;
});
});
}
/*
* Return results for completion
* Option can be used to filter by file, query, ...
*/
CodeComplete.prototype.get = function(options) {
var that = this;
options = _.defaults({}, options || {}, {
// Filter name with query
'query': "",
// Filter filepath with file
'file': null
});
var results = [];
// Get all results from all the handlers
return Q.allSettled(_.map(this.handlers, function(handler, i) {
return Q(handler.handler(options, handler.name)).then(function(_results) {
results = results.concat(_results);
});
})).then(function(handlerStates) {
results = _.chain(results)
// Filter results
.filter(function(result) {
// Check format
if (!result.name) return false;
// Filter the result
if (options.file && result.file.indexOf(options.file) != 0) return false
if (options.query && result.name.indexOf(options.query) != 0) return false;
return true;
})
// Remove doublons
.uniq(function(result) {
return result.name;
})
// Order results
.sortBy(function(result) {
return result.score;
})
.value();
return {
// List of results
'results': results,
// State of handlers
'handlers': _.map(that.handlers, function(handler, i) {
return {
'handler': handler.name,
'state': handlerStates[i].state,
'error': handlerStates[i].reason ? handlerStates[i].reason.message : undefined
};
})
};
})
}
// Exports
exports.CodeComplete = CodeComplete;