.
*/
(function () {
// constants
var firstCharacterPos = {'line': 0, 'ch': 0},
sel_no_scroll = {'scroll': false};
codeq.makeHinter = function (jqHints, jqEditor, editor, hintDefs, planDef) {
var hintCounter = 0, // for generating unique class-names
hintCleaners = [],
planIdx = 0,
clearHints = function () {
var i;
for (i = hintCleaners.length - 1; i >= 0; i--) {
hintCleaners[i]();
}
hintCleaners.length = 0;
hintCounter = 0;
},
addMark = function (start, end) {
var posStart = editor.posFromIndex(start),
posEnd = editor.posFromIndex(end),
doc = editor.getDoc(),
mark = doc.markText(posStart, posEnd, {'className': 'editor-mark _emark_' + hintCounter}),
result = {'start': posStart, 'end': posEnd, 'mark': mark, 'className': '_emark_' + hintCounter};
hintCleaners.push(function () { mark.clear(); mark = null; doc = null; result.mark = null; });
hintCounter++;
return result;
},
processTemplate = function (template, args) {
if (!args)
return template;
return template.replace(/\[%=(\w+)%\]/g, function(match, name) {
return args[name].toString()
.replace(/&/g, '&')
.replace(//g, '>');
});
},
typeHandlers = {
'static': function (type, template, serverHint) {
var args = serverHint ? serverHint.args : null,
jqContainer, jqButton, i, N, tmpl, tmplIsObject;
if (template instanceof Array) { // unwrap the template if there's only one
if (template.length == 0) template = '';
else if (template.length == 1) template = template[0] + ''; // it must be a string
}
if (template instanceof Array) {
codeq.log.debug('Processing an array of static hints');
jqContainer = $('
');
jqButton = $('
');
jqHints.append(jqContainer);
N = template.length;
tmpl = template[0];
tmplIsObject = (typeof tmpl === 'object') && (tmpl !== null);
jqContainer.append('
' + processTemplate((tmplIsObject ? tmpl.message : tmpl) || '', args) + '
');
jqContainer.append(jqButton);
jqButton.text(tmplIsObject && tmpl.linkText ? tmpl.linkText : 'More...'); // TODO: translate "more"
i = 1;
jqButton.on('click', function () {
var tmpl = template[i],
tmplIsObject = (typeof tmpl === 'object') && (tmpl !== null),
jqNext = $('
' + processTemplate((tmplIsObject ? tmpl.message : tmpl) || '', args) + '
');
i++;
if (i < N) {
jqButton.before(jqNext);
jqButton.text(tmplIsObject && tmpl.linkText ? tmpl.linkText : 'More...'); // TODO: translate "more"
}
else {
jqButton.remove();
jqContainer.append(jqNext);
}
});
}
else {
codeq.log.debug('Processing a single static hint');
jqHints.append('
' + processTemplate(template, args) + '
');
}
// no hint cleaner here, a static hint remains on the screen
},
'popup': function (type, template, serverHint) {
codeq.log.debug('Processing popup hint');
var message = processTemplate(template, serverHint.args),
mark = addMark(serverHint.start, serverHint.end), // add the mark
jqMark = jqEditor.find('.' + mark.className);
jqMark.popover({'content': message, 'html': true, 'placement': 'auto bottom', 'trigger': 'hover focus click', 'container': 'body'});
hintCleaners.push(function () { if (jqMark) { jqMark.popover('destroy'); jqMark = null; } });
mark.mark.on('', function () {});
},
'dropdown': function (type, template, serverHint) {
codeq.log.debug('Processing dropdown hint');
var completion = null, // the completion object, created in showHint()
close = function () {
if (completion) {
completion.close();
completion = null;
}
};
if (/*(editor.listSelections().length > 1) ||*/ editor.somethingSelected()) {
// showHint() doesn't work if a selection is activeparts
editor.setSelection(firstCharacterPos, firstCharacterPos, sel_no_scroll); // deselect anything
}
editor.showHint({
hint: function () {
var hints = {
list: serverHint.choices,
from: editor.posFromIndex(serverHint.start),
to: editor.posFromIndex(serverHint.end)
};
completion = editor.state.completionActive;
return hints;
},
completeOnSingleClick: true,
completeSingle: false
});
hintCleaners.push(close);
}
};
return {
/** Display the next "planning" hint and return whether there are
* any more available.
*/
'planNext': function () {
if (planIdx < planDef.length) {
typeHandlers['static']('static', planDef[planIdx], null);
planIdx++;
}
return planIdx < planDef.length;
},
/**
* Processes and display appropriately the server hints.
* TODO: sort hints so static and popup hints come first, and a (single) drop-down hint last
*
* @param {ServerHint[]} serverHints an array of hints from the server
*/
'handle': function (serverHints) {
var n = serverHints.length,
/** number */ i,
/** ServerHint */ serverHint,
/** HintDefinition */ hintDef,
hintType, hintTemplate, t, fn, indices;
clearHints();
mainLoop:
for (i = 0; i < n; i++) {
serverHint = serverHints[i];
hintDef = hintDefs[serverHint.id];
if (!hintDef) {
codeq.log.error('Undefined hint ' + serverHint.id);
continue;
}
if (serverHint.indices) {
indices = serverHint.indices;
for (i = 0; i < indices.length; i++) {
hintDef = hintDef[indices[i]];
if (!hintDef) {
codeq.log.error('Undefined hint ' + serverHint.id + ' with indices ' + serverHint.indices);
continue mainLoop;
}
}
}
t = typeof hintDef;
if ((t === 'string') || (hintDef instanceof Array)) {
hintType = 'static';
hintTemplate = hintDef;
}
else if (t === 'object') {
hintType = hintDef.type;
hintTemplate = hintDef.message;
}
else {
codeq.log.error('Unsupported hint definition: ' + t);
continue;
}
fn = typeHandlers[hintType];
if (!fn) codeq.log.error('Unsupported hint type: ' + hintType);
else fn(hintType, hintTemplate, serverHint);
}
},
'destroy': function () {
clearHints();
jqHints.empty();
jqHints = null;
jqEditor = null;
editor = null;
}
};
};
})();