yasme
Version:
Yet Another ShapeMap Editor
305 lines (275 loc) • 10.9 kB
JavaScript
;
var $ = require("jquery"),
utils = require("../utils/baseUtils.js"),
yutils = require("yasgui-utils"),
Trie = require("../../lib/trie.js"),
YASME = require("../main.js");
module.exports = function(YASME, yasme) {
var completionNotifications = {};
var completers = {};
var tries = {};
yasme.on("cursorActivity", function(yasme, eventInfo) {
autoComplete(true);
});
yasme.on("change", function() {
var needPossibleAdjustment = [];
for (var notificationName in completionNotifications) {
if (completionNotifications[notificationName].is(":visible")) {
needPossibleAdjustment.push(completionNotifications[notificationName]);
}
}
if (needPossibleAdjustment.length > 0) {
//position completion notifications
var scrollBar = $(yasme.getWrapperElement()).find(".CodeMirror-vscrollbar");
var offset = 0;
if (scrollBar.is(":visible")) {
offset = scrollBar.outerWidth();
}
needPossibleAdjustment.forEach(function(notification) {
notification.css("right", offset);
});
}
});
/**
* Store bulk completions in memory as trie, and store these in localstorage as well (if enabled)
*
* @method doc.storeBulkCompletions
* @param completions {array}
*/
var storeBulkCompletions = function(completer, completions) {
// store array as trie
tries[completer.name] = new Trie();
for (var i = 0; i < completions.length; i++) {
tries[completer.name].insert(completions[i]);
}
// store in localstorage as well
var storageId = utils.getPersistencyId(yasme, completer.persistent);
if (storageId) yutils.storage.set(storageId, completions, "month", yasme.options.onQuotaExceeded);
};
var initCompleter = function(name, completionInit) {
var completer = (completers[name] = new completionInit(yasme, name));
completer.name = name;
if (completer.bulk) {
var storeArrayAsBulk = function(suggestions) {
if (suggestions && suggestions instanceof Array && suggestions.length > 0) {
storeBulkCompletions(completer, suggestions);
}
};
if (completer.get instanceof Array) {
// we don't care whether the completions are already stored in
// localstorage. just use this one
storeArrayAsBulk(completer.get);
} else {
// if completions are defined in localstorage, use those! (calling the
// function may come with overhead (e.g. async calls))
var completionsFromStorage = null;
var persistencyIdentifier = utils.getPersistencyId(yasme, completer.persistent);
if (persistencyIdentifier) completionsFromStorage = yutils.storage.get(persistencyIdentifier);
if (completionsFromStorage && completionsFromStorage.length > 0) {
storeArrayAsBulk(completionsFromStorage);
} else {
// nothing in storage. check whether we have a function via which we
// can get our prefixes
if (completer.get instanceof Function) {
if (completer.async) {
completer.get(null, storeArrayAsBulk);
} else {
storeArrayAsBulk(completer.get());
}
}
}
}
}
};
var autoComplete = function(fromAutoShow) {
if (yasme.somethingSelected()) return;
var tryHintType = function(completer) {
if (
fromAutoShow && // from autoShow, i.e. this gets called each time the editor content changes
(!completer.autoShow || // autoshow for this particular type of autocompletion is -not- enabled
(!completer.bulk && completer.async)) // async is enabled (don't want to re-do ajax-like request for every editor change)
) {
return false;
}
var hintConfig = {
closeCharacters: /(?=a)b/,
completeSingle: false
};
if (!completer.bulk && completer.async) {
hintConfig.async = true;
}
var wrappedHintCallback = function(yasme, callback) {
return getCompletionHintsObject(completer, callback);
};
var result = YASME.showHint(yasme, wrappedHintCallback, hintConfig);
return true;
};
for (var completerName in completers) {
if ($.inArray(completerName, yasme.options.autocompleters) == -1) continue; //this completer is disabled
var completer = completers[completerName];
if (!completer.isValidCompletionPosition) continue; //no way to check whether we are in a valid position
if (!completer.isValidCompletionPosition()) {
//if needed, fire callbacks for when we are -not- in valid completion position
if (completer.callbacks && completer.callbacks.invalidPosition) {
completer.callbacks.invalidPosition(yasme, completer);
}
//not in a valid position, so continue to next completion candidate type
continue;
}
// run valid position handler, if there is one (if it returns false, stop the autocompletion!)
if (completer.callbacks && completer.callbacks.validPosition) {
if (completer.callbacks.validPosition(yasme, completer) === false) continue;
}
var success = tryHintType(completer);
if (success) break;
}
};
var getCompletionHintsObject = function(completer, callback) {
var getSuggestionsFromToken = function(partialToken) {
var stringToAutocomplete = partialToken.autocompletionString || partialToken.string;
var suggestions = [];
if (tries[completer.name]) {
suggestions = tries[completer.name].autoComplete(stringToAutocomplete);
} else if (typeof completer.get == "function" && completer.async == false) {
suggestions = completer.get(stringToAutocomplete);
} else if (typeof completer.get == "object") {
var partialTokenLength = stringToAutocomplete.length;
for (var i = 0; i < completer.get.length; i++) {
var completion = completer.get[i];
if (completion.slice(0, partialTokenLength) == stringToAutocomplete) {
suggestions.push(completion);
}
}
}
return getSuggestionsAsHintObject(suggestions, completer, partialToken);
};
var token = yasme.getCompleteToken();
if (completer.preProcessToken) {
token = completer.preProcessToken(token);
}
if (token) {
// use custom completionhint function, to avoid reaching a loop when the
// completionhint is the same as the current token
// regular behaviour would keep changing the codemirror dom, hence
// constantly calling this callback
if (!completer.bulk && completer.async) {
var wrappedCallback = function(suggestions) {
callback(getSuggestionsAsHintObject(suggestions, completer, token));
};
completer.get(token, wrappedCallback);
} else {
return getSuggestionsFromToken(token);
}
}
};
/**
* get our array of suggestions (strings) in the codemirror hint format
*/
var getSuggestionsAsHintObject = function(suggestions, completer, token) {
var hintList = [];
var startChar;
//For the wikidata completer we recive the {text, displayText} object
if(completer.name == 'wikibase' || completer.name == 'prefixesAndKeywords'){
for (var i = 0; i < suggestions.length; i++) {
hintList.push({
text: suggestions[i].text,
displayText: suggestions[i].displayText,
hint: selectHint
});
}
if(completer.name == 'wikibase'){
//Do not replace the prefix
var prefix = token.string.split(':')[0]
startChar = token.start + prefix.length + 1
}else{
startChar = token.start
}
}else {
for (var i = 0; i < suggestions.length; i++) {
var suggestedString = suggestions[i];
if (completer.postProcessToken) {
suggestedString = completer.postProcessToken(token, suggestedString);
}
hintList.push({
text: suggestedString,
displayText: suggestedString,
hint: selectHint
});
}
startChar = token.start
}
var cur = yasme.getCursor();
var returnObj = {
completionToken: token.string,
list: hintList,
from: {
line: cur.line,
ch: startChar
},
to: {
line: cur.line,
ch: token.end
}
};
//if we have some autocompletion handlers specified, add these these to the object. Codemirror will take care of firing these
if (completer.callbacks) {
for (var callbackName in completer.callbacks) {
if (completer.callbacks[callbackName]) {
YASME.on(returnObj, callbackName, completer.callbacks[callbackName]);
}
}
}
return returnObj;
};
return {
init: initCompleter,
completers: completers,
notifications: {
getEl: function(completer) {
return $(completionNotifications[completer.name]);
},
show: function(yasme, completer) {
//only draw when the user needs to use a keypress to summon autocompletions
if (!completer.autoshow) {
if (!completionNotifications[completer.name])
completionNotifications[completer.name] = $("<div class='completionNotification'></div>");
completionNotifications[completer.name]
.show()
.text("Press CTRL - <spacebar> to autocomplete")
.appendTo($(yasme.getWrapperElement()));
}
},
hide: function(yasme, completer) {
if (completionNotifications[completer.name]) {
completionNotifications[completer.name].hide();
}
}
},
autoComplete: autoComplete,
getTrie: function(completer) {
return typeof completer == "string" ? tries[completer] : tries[completer.name];
}
};
};
/**
* function which fires after the user selects a completion. this function checks whether we actually need to store this one (if completion is same as current token, don't do anything)
*/
var selectHint = function(yasme, data, completion) {
if (completion.text != yasme.getTokenAt(yasme.getCursor()).string) {
yasme.replaceRange(completion.text, data.from, data.to);
}
};
//
//module.exports = {
// preprocessPrefixTokenForCompletion: preprocessPrefixTokenForCompletion,
// postprocessResourceTokenForCompletion: postprocessResourceTokenForCompletion,
// preprocessResourceTokenForCompletion: preprocessResourceTokenForCompletion,
// showCompletionNotification: showCompletionNotification,
// hideCompletionNotification: hideCompletionNotification,
// autoComplete: autoComplete,
// autocompleteVariables: autocompleteVariables,
// fetchFromPrefixCc: fetchFromPrefixCc,
// fetchFromLov: fetchFromLov,
//// storeBulkCompletions: storeBulkCompletions,
// loadBulkCompletions: loadBulkCompletions,
//};