summaryrefslogtreecommitdiff
path: root/js/codeq/hint.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/codeq/hint.js')
-rw-r--r--js/codeq/hint.js193
1 files changed, 193 insertions, 0 deletions
diff --git a/js/codeq/hint.js b/js/codeq/hint.js
new file mode 100644
index 0000000..c22f39f
--- /dev/null
+++ b/js/codeq/hint.js
@@ -0,0 +1,193 @@
+/**
+ * Creates a hint handler, displaying hints inside the provided jqHints <div>.
+ */
+
+(function () {
+ // constants
+ var firstCharacterPos = {'line': 0, 'ch': 0},
+ sel_no_scroll = {'scroll': false};
+
+ codeq.makeHinter = function (jqHints, jqEditor, editor, hintDefs) {
+ var hintCounter = 0, // for generating unique class-names
+ hintCleaners = [],
+
+ 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];
+ });
+ },
+
+ typeHandlers = {
+ 'static': function (type, template, serverHint) {
+ var jqContainer, jqButton, promise, i, lastIndex;
+ 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] + '';
+ }
+ if (template instanceof Array) {
+ codeq.log.debug('Processing an array of static hints');
+ jqContainer = $('<div class="hint-static-group"></div>');
+ jqButton = $('<button type="button">More...</button>'); // TODO: translate "more"
+ jqHints.append(jqContainer);
+ lastIndex = template.length - 1;
+ promise = Q();
+ for (i = 0; i <= lastIndex; i++) {
+ promise = promise.then((function (tmpl, index) {
+ return Q.Promise(function (resolve, reject) {
+ var message = processTemplate(tmpl, serverHint.args),
+ onClick;
+ jqContainer.append('<div class="hint-static">' + message + '</div>');
+ if (index < lastIndex) {
+ onClick = function () {
+ jqButton.off('click', onClick);
+ jqButton.remove();
+ resolve();
+ };
+ jqContainer.append(jqButton);
+ jqButton.on('click', onClick);
+ }
+ else {
+ resolve();
+ }
+ });
+ })(template[i], i));
+ }
+ promise.done();
+ }
+ else {
+ codeq.log.debug('Processing a single static hint');
+ var message = processTemplate(template, serverHint.args);
+ jqHints.append('<div class="hint-static">' + message + '</div>');
+ }
+ // 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 {
+ /**
+ * 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 === 'object') {
+ hintType = hintDef.type;
+ hintTemplate = hintDef.message;
+ }
+ else if (t === 'string') {
+ hintType = 'static';
+ hintTemplate = hintDef;
+ }
+ 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;
+ }
+ };
+ };
+})(); \ No newline at end of file