UNPKG

autosuggestion

Version:

  Generates suggestions for text completion.  

187 lines 8.18 kB
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