autosuggestion
Version:
  Generates suggestions for text completion.  
187 lines • 8.18 kB
JavaScript
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
import { Node } from './node';
import { LookupNode } from './lookup';
import { NormalizePattern } from './util';
import { isWord, isLookup } from './typegaurds';
var Trie = /** @class */ (function (_super) {
__extends(Trie, _super);
function Trie(dictionary, patterns) {
if (patterns === void 0) { patterns = []; }
var _this = _super.call(this, null) || this;
_this.dictionary = dictionary;
// add provided patterns to our trie.
for (var _i = 0, patterns_1 = patterns; _i < patterns_1.length; _i++) {
var pattern = patterns_1[_i];
_this.add(pattern);
}
return _this;
}
Trie.prototype.add = function (pattern) {
var _a;
var words = NormalizePattern(pattern);
if (words.length === 0)
return;
// TODO validate pattern.
// TODO handle empty strings as Words by skipping.
// (gaurantees each word has at least one character)
var node = this;
// this ensures that the root node of the Trie does not have any next.chars
// rather, the null root acts as the last char of the previous word in a pattern
// this is a simplifying structure for the algorithm.
if (isWord(words[0])) {
(_a = this._addFirstCharOfNextWord(this, words), node = _a.node, words = _a.pattern);
}
this._add(node, words);
};
Trie.prototype._add = function (node, pattern, isLastWord) {
if (isLastWord === void 0) { isLastWord = false; }
if (pattern.length === 0)
return;
if (pattern.length === 1)
isLastWord = true;
var nextNodes = [];
var next = pattern[0];
if (isWord(next))
nextNodes = [this._addChars(node, next, isLastWord)];
if (isLookup(next))
nextNodes = this._addLookup(node, next, isLastWord);
if (isLastWord)
return;
pattern = pattern.slice(1);
next = pattern[0];
for (var _i = 0, nextNodes_1 = nextNodes; _i < nextNodes_1.length; _i++) {
var nextNode = nextNodes_1[_i];
var nextPattern = pattern;
if (isWord(next)) {
var result = this._addFirstCharOfNextWord(nextNode, pattern);
nextNode = result.node;
nextPattern = result.pattern;
}
this._add(nextNode, nextPattern);
}
};
Trie.prototype._addFirstCharOfNextWord = function (node, pattern) {
var word = pattern[0];
var c = word[0];
// check if this node has already been made
if (!(c in node.next.word))
node.next.word[c] = new Node(c);
node = node.next.word[c];
if (word.length === 1 && pattern.length === 1) {
node.end = true;
return { node: node, pattern: [] };
}
pattern = [word.substr(1)].concat(pattern.slice(1));
return { node: node, pattern: pattern };
};
Trie.prototype._addChars = function (node, word, isLastWord) {
if (isLastWord === void 0) { isLastWord = true; }
if (word.length === 0)
return node;
var c = word[0];
if (!node.next.char[c])
node.next.char[c] = new Node(c);
if (word.length === 1 && isLastWord)
node.next.char[c].end = true;
// recurse until all has been consumed.
if (word.length > 1)
return this._addChars(node.next.char[c], word.slice(1), isLastWord);
return node.next.char[c];
};
Trie.prototype._addLookup = function (node, lookup, isLastWord) {
if (isLastWord === void 0) { isLastWord = true; }
var nodes = [];
for (var _i = 0, _a = Object.entries(lookup); _i < _a.length; _i++) {
var _b = _a[_i], alias = _b[0], contexts = _b[1];
// normalize contexts to always be an array
if (!Array.isArray(contexts))
contexts = [contexts];
var tries = [];
for (var _c = 0, contexts_1 = contexts; _c < contexts_1.length; _c++) {
var context = contexts_1[_c];
var trie = this.dictionary.contexts.get(context);
if (!trie)
throw new Error("No such context '" + context + "'"); // TODO make this a type
tries.push(trie);
}
var lookupNode = new LookupNode(alias, tries);
if (isLastWord)
lookupNode.end = true;
node.next.lookup[alias] = lookupNode;
nodes.push(lookupNode);
}
return nodes;
};
Trie.prototype.remove = function (pattern) { };
/**
* Given a sequence of input tokens, returns an array of suggested completions.
*
* @param input a sequence of input tokens.
* @param [lookahead=0] how many tokens to resolve in lookups which occur immediately after input.
*/
Trie.prototype.suggest = function (input, lookahead) {
if (lookahead === void 0) { lookahead = 0; }
var suggestions = [];
// normalize input to be an array (if only given a string)
if (!Array.isArray(input))
input = [input];
// find matches with no remainder and extract their lookup-stacks
var stacks = this.matchPattern(input).filter(function (m) { return m.remainder.length === 0; }).map(function (m) { return m.nodes; });
for (var _i = 0, stacks_1 = stacks; _i < stacks_1.length; _i++) {
var stack = stacks_1[_i];
suggestions = suggestions.concat(this._unwind(stack, input, lookahead));
}
return suggestions;
};
Trie.prototype._unwind = function (stack, input, lookahead) {
if (lookahead === void 0) { lookahead = 0; }
var suggestions = stack[0].completePattern(__spreadArrays(input));
// essentially reshape the `suggestions` array s.t. each resulting array of
// suggestions from each subsequent node in the lookup-stack is concat'd.
for (var _i = 0, _a = stack.slice(1); _i < _a.length; _i++) {
var node = _a[_i];
for (var _b = 0, _c = node.completePattern([]); _b < _c.length; _b++) {
var suggestion = _c[_b];
var tmp = [];
for (var _d = 0, suggestions_1 = suggestions; _d < suggestions_1.length; _d++) {
var suggestion_i = suggestions_1[_d];
tmp.push(suggestion_i.concat(suggestion));
}
suggestions = tmp;
}
}
// return immediately if we don't require a lookahead.
if (lookahead === 0)
return suggestions;
// for each resulting suggestion, enforce a lookahead from the input offset
var resolvedSuggestions = [];
for (var _e = 0, suggestions_2 = suggestions; _e < suggestions_2.length; _e++) {
var suggestion = suggestions_2[_e];
resolvedSuggestions = resolvedSuggestions
.concat(suggestion.resolveLookups(input, lookahead));
}
return resolvedSuggestions;
};
return Trie;
}(Node));
export { Trie };
//# sourceMappingURL=trie.js.map