@atlaskit/editor-plugin-autocomplete 9.0.0 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +133 -0
- package/dist/cjs/pm-plugins/autocomplete-plugin.js +564 -67
- package/dist/cjs/pm-plugins/debug-mode.js +28 -2
- package/dist/cjs/pm-plugins/inline-code-harvester.js +576 -0
- package/dist/cjs/pm-plugins/text-predictor.js +437 -178
- package/dist/es2019/pm-plugins/autocomplete-plugin.js +515 -52
- package/dist/es2019/pm-plugins/debug-mode.js +27 -1
- package/dist/es2019/pm-plugins/inline-code-harvester.js +455 -0
- package/dist/es2019/pm-plugins/text-predictor.js +223 -3
- package/dist/esm/pm-plugins/autocomplete-plugin.js +562 -67
- package/dist/esm/pm-plugins/debug-mode.js +27 -1
- package/dist/esm/pm-plugins/inline-code-harvester.js +572 -0
- package/dist/esm/pm-plugins/text-predictor.js +437 -178
- package/dist/types/pm-plugins/autocomplete-plugin.d.ts +37 -0
- package/dist/types/pm-plugins/debug-mode.d.ts +34 -0
- package/dist/types/pm-plugins/inline-code-harvester.d.ts +146 -0
- package/dist/types/pm-plugins/text-predictor.d.ts +105 -3
- package/package.json +2 -2
- package/src/pm-plugins/autocomplete-plugin.ts +589 -48
- package/src/pm-plugins/debug-mode.ts +47 -1
- package/src/pm-plugins/inline-code-harvester.ts +561 -0
- package/src/pm-plugins/text-predictor.ts +274 -5
|
@@ -90,7 +90,7 @@ var printLegend = function printLegend(verbose) {
|
|
|
90
90
|
// eslint-disable-next-line no-console
|
|
91
91
|
console.log('%cPlanes%c [CTC:init] loads · [CTC:signal] semantic/network · [CTC:model] primes/exact · [CTC:model-cost] prefill/decode · [CTC:readiness] deadline progress', CTC_STYLES.section, CTC_STYLES.body);
|
|
92
92
|
// eslint-disable-next-line no-console
|
|
93
|
-
console.log('%cInspect%c __atlCtcDebug__.enable("verbose") · .disable()', CTC_STYLES.section, CTC_STYLES.body);
|
|
93
|
+
console.log('%cInspect%c __atlCtcDebug__.enable("verbose") · .disable() · .harvest() (session inline code) · .session() (L1 boosts) — both narrow to a prefix, e.g. .session("poll")', CTC_STYLES.section, CTC_STYLES.body);
|
|
94
94
|
// eslint-disable-next-line no-console
|
|
95
95
|
console.groupEnd();
|
|
96
96
|
};
|
|
@@ -138,5 +138,31 @@ export var isAutocompleteDebugVerbose = function isAutocompleteDebugVerbose() {
|
|
|
138
138
|
return (_getDebugApi$isVerbos = (_getDebugApi2 = getDebugApi()) === null || _getDebugApi2 === void 0 ? void 0 : _getDebugApi2.isVerbose()) !== null && _getDebugApi$isVerbos !== void 0 ? _getDebugApi$isVerbos : false;
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Hang the harvest snapshot off the console API.
|
|
143
|
+
*
|
|
144
|
+
* Unlike the log helpers this is available whether or not debug is enabled:
|
|
145
|
+
* inspecting state on demand is not logging, and asking someone to turn on
|
|
146
|
+
* logging and retype to find out what the session already holds defeats the
|
|
147
|
+
* point of being able to ask.
|
|
148
|
+
*/
|
|
149
|
+
export var registerCtcHarvestInspector = function registerCtcHarvestInspector(inspect) {
|
|
150
|
+
var api = getDebugApi();
|
|
151
|
+
if (api) {
|
|
152
|
+
api.harvest = inspect;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Hang the L1 session-boost snapshot off the console API, on the same terms as
|
|
158
|
+
* `registerCtcHarvestInspector`: available whether or not logging is on.
|
|
159
|
+
*/
|
|
160
|
+
export var registerCtcSessionInspector = function registerCtcSessionInspector(inspect) {
|
|
161
|
+
var api = getDebugApi();
|
|
162
|
+
if (api) {
|
|
163
|
+
api.session = inspect;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
141
167
|
// Eagerly install so the console API is available on load, regardless of call order.
|
|
142
168
|
getDebugApi();
|
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
3
|
+
import _toArray from "@babel/runtime/helpers/toArray";
|
|
4
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
5
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
6
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
7
|
+
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
8
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
9
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
10
|
+
/**
|
|
11
|
+
* Inline-code harvester — the Class B candidate source.
|
|
12
|
+
*
|
|
13
|
+
* Class A terms (Atlassian, Confluence) already carry tenant frequencies, a word
|
|
14
|
+
* vector and canonical token ids, so they compete inside the scored pool. Class B
|
|
15
|
+
* terms — `ml-studio` and friends — exist only in the session and cannot be
|
|
16
|
+
* suggested at all today: `incrementSessionFreq` only touches trie nodes that
|
|
17
|
+
* already exist, so an unknown session word is silently discarded.
|
|
18
|
+
*
|
|
19
|
+
* Two sources carry the signal. Assistant replies arrive as markdown strings and
|
|
20
|
+
* keep their backticks; the live document has had its backticks consumed by the
|
|
21
|
+
* text-formatting input rule, so its spans are found through the `code` mark
|
|
22
|
+
* instead. Human chat messages arrive as ADF and are flattened to bare text, so
|
|
23
|
+
* they contribute no spans — which is why no author check is needed here.
|
|
24
|
+
*
|
|
25
|
+
* A harvested surface cannot be scored: there is no frequency to rank it, no
|
|
26
|
+
* vector to place it in context and no canonical token ids to price it under the
|
|
27
|
+
* model. Being marked as code is the whole of the evidence, so what stands in for
|
|
28
|
+
* a score is where the surface is allowed to speak — only on a prefix no
|
|
29
|
+
* vocabulary reached, and only once the scored path has finished with it.
|
|
30
|
+
*
|
|
31
|
+
* Sightings are still counted, per source and by different rules, but they do
|
|
32
|
+
* not admit a surface. They order the set under eviction, and they pick between
|
|
33
|
+
* two surfaces that complete the same typed prefix — see `HarvestedTerm` and
|
|
34
|
+
* `findHarvestedCompletion`.
|
|
35
|
+
*
|
|
36
|
+
* What the session is holding is visible at any time from the console:
|
|
37
|
+
* `__atlCtcDebug__.harvest()`, or `__atlCtcDebug__.harvest('ml-s')` to ask what
|
|
38
|
+
* a prefix would be offered — see `inspectInlineCodeHarvest`.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { CTC_STYLES, ctcTag, isAutocompleteDebugEnabled, isAutocompleteDebugVerbose, registerCtcHarvestInspector } from './debug-mode';
|
|
42
|
+
import { lookupVocabularySource } from './text-predictor';
|
|
43
|
+
|
|
44
|
+
/** Longer than this is a command or a path, not a term anyone wants completed. */
|
|
45
|
+
var MAX_IDENTIFIER_LENGTH = 50;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Longer than the predictor's DISPLAY_MIN_PREFIX_LENGTH of 3.
|
|
49
|
+
*
|
|
50
|
+
* The scored path can afford three characters because it has a posterior and a
|
|
51
|
+
* winner margin to answer for the fourth. This path has neither, so it asks the
|
|
52
|
+
* user to commit further before it will guess.
|
|
53
|
+
*/
|
|
54
|
+
var MIN_TYPED_PREFIX_LENGTH = 4;
|
|
55
|
+
|
|
56
|
+
/** Mirrors the predictor's MIN_SUGGESTION_LENGTH. */
|
|
57
|
+
var MIN_GHOST_LENGTH = 3;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Sightings needed before a surface may be offered, counting both sources.
|
|
61
|
+
*
|
|
62
|
+
* One, because asking for two reads the replies this exists for as no evidence
|
|
63
|
+
* at all: a reply answering "what are our service names" names each one exactly
|
|
64
|
+
* once, and ten terms mentioned once each is the normal shape of the answer, not
|
|
65
|
+
* a weak signal. What keeps noise out is not a count — see `isDisplayEligible`.
|
|
66
|
+
*/
|
|
67
|
+
var MIN_OCCURRENCES = 1;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Cap on the harvested set.
|
|
71
|
+
*
|
|
72
|
+
* Not a memory bound — surfaces are capped at 50 characters. It is there so a
|
|
73
|
+
* long session's early terms cannot sit in the set competing with what is being
|
|
74
|
+
* discussed now, and so the display path's scan stays bounded. Provisional:
|
|
75
|
+
* size it off the funnel numbers from a branch deploy rather than this guess.
|
|
76
|
+
*/
|
|
77
|
+
var MAX_HARVESTED_TERMS = 200;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A surface shorter than a qualifying prefix plus a qualifying ghost can never
|
|
81
|
+
* be shown. Recorded rather than filtered, so the funnel shows how much of the
|
|
82
|
+
* harvest is unusable for that reason alone.
|
|
83
|
+
*/
|
|
84
|
+
var DISPLAYABLE_MIN_LENGTH = MIN_TYPED_PREFIX_LENGTH + MIN_GHOST_LENGTH;
|
|
85
|
+
var FENCED_REGION_REGEX = /```(?:[\0-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF])*?```/g;
|
|
86
|
+
var INLINE_CODE_SPAN_REGEX = /`((?:[\0-\t\x0B-_a-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+)`/g;
|
|
87
|
+
var WHITESPACE_REGEX = /[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]/;
|
|
88
|
+
var STARTS_WITH_LETTER_REGEX = /^(?:[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088F\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5C\u0C5D\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDC-\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C8A\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7DC\uA7F1-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDDC0-\uDDF3\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD40-\uDD59\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDD4A-\uDD65\uDD6F-\uDD85\uDE80-\uDEA9\uDEB0\uDEB1\uDEC2-\uDEC7\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDF70-\uDF81\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE3F\uDE40\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61\uDF80-\uDF89\uDF8B\uDF8E\uDF90-\uDFB5\uDFB7\uDFD1\uDFD3]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDEB8\uDF00-\uDF1A\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCDF\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8\uDFC0-\uDFE0]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDB0-\uDDDB\uDEE0-\uDEF2\uDF02\uDF04-\uDF10\uDF12-\uDF33\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD80E\uD80F\uD81C-\uD822\uD840-\uD868\uD86A-\uD86D\uD86F-\uD872\uD874-\uD879\uD880-\uD883\uD885-\uD88C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2F\uDC41-\uDC46\uDC60-\uDFFF]|\uD810[\uDC00-\uDFFA]|\uD811[\uDC00-\uDE46]|\uD818[\uDD00-\uDD1D]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE70-\uDEBE\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDD40-\uDD6C\uDE40-\uDE7F\uDEA0-\uDEB8\uDEBB-\uDED3\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3\uDFF2\uDFF3]|\uD823[\uDC00-\uDCD5\uDCFF-\uDD1E\uDD80-\uDDF2]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD32\uDD50-\uDD52\uDD55\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD837[\uDF00-\uDF1E\uDF25-\uDF2A]|\uD838[\uDC30-\uDC6D\uDD00-\uDD2C\uDD37-\uDD3D\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB]|\uD839[\uDCD0-\uDCEB\uDDD0-\uDDED\uDDF0\uDEC0-\uDEDE\uDEE0-\uDEE2\uDEE4\uDEE5\uDEE7-\uDEED\uDEF0-\uDEF4\uDEFE\uDEFF\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEAD\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0\uDFF0-\uDFFF]|\uD87B[\uDC00-\uDE5D]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A\uDF50-\uDFFF]|\uD88D[\uDC00-\uDC79])/;
|
|
89
|
+
|
|
90
|
+
/** Why this surface was chosen over the others that completed the prefix. */
|
|
91
|
+
|
|
92
|
+
var emptyFunnel = function emptyFunnel() {
|
|
93
|
+
return {
|
|
94
|
+
accepted: 0,
|
|
95
|
+
rejectedInFence: 0,
|
|
96
|
+
rejectedKnownL2: 0,
|
|
97
|
+
rejectedKnownL3: 0,
|
|
98
|
+
rejectedNonAlphaStart: 0,
|
|
99
|
+
rejectedTooLong: 0,
|
|
100
|
+
rejectedWhitespace: 0,
|
|
101
|
+
spansFound: 0
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Session-scoped, and only correct because the plugin resets it on destroy: the
|
|
106
|
+
// map holds user and assistant content, so surviving an unmount would leak one
|
|
107
|
+
// conversation's terms into the next.
|
|
108
|
+
var harvested = new Map();
|
|
109
|
+
// Two funnels because the two sources are read on different schedules. Ingested
|
|
110
|
+
// text is seen once, so its funnel accumulates; the document is re-walked on
|
|
111
|
+
// every word boundary, so its funnel is replaced to stay a description of the
|
|
112
|
+
// document rather than of how long the session has run.
|
|
113
|
+
var contextFunnel = emptyFunnel();
|
|
114
|
+
var documentFunnel = emptyFunnel();
|
|
115
|
+
var evictedTerms = 0;
|
|
116
|
+
var lastLoggedSignature = '';
|
|
117
|
+
var collectFencedRanges = function collectFencedRanges(text) {
|
|
118
|
+
var ranges = [];
|
|
119
|
+
var _iterator = _createForOfIteratorHelper(text.matchAll(FENCED_REGION_REGEX)),
|
|
120
|
+
_step;
|
|
121
|
+
try {
|
|
122
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
123
|
+
var match = _step.value;
|
|
124
|
+
if (match.index !== undefined) {
|
|
125
|
+
ranges.push([match.index, match.index + match[0].length]);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
_iterator.e(err);
|
|
130
|
+
} finally {
|
|
131
|
+
_iterator.f();
|
|
132
|
+
}
|
|
133
|
+
return ranges;
|
|
134
|
+
};
|
|
135
|
+
var isInsideFence = function isInsideFence(ranges, index) {
|
|
136
|
+
return ranges.some(function (_ref) {
|
|
137
|
+
var _ref2 = _slicedToArray(_ref, 2),
|
|
138
|
+
start = _ref2[0],
|
|
139
|
+
end = _ref2[1];
|
|
140
|
+
return index >= start && index < end;
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/** The cleaned surface if it survives intake, or null. Records the rejection. */
|
|
145
|
+
var admitCandidate = function admitCandidate(raw, funnel) {
|
|
146
|
+
// Markdown permits one space of padding inside the ticks, so trim before
|
|
147
|
+
// testing for the internal whitespace that separates a term from a command.
|
|
148
|
+
var candidate = raw.trim();
|
|
149
|
+
if (candidate.length === 0 || WHITESPACE_REGEX.test(candidate)) {
|
|
150
|
+
funnel.rejectedWhitespace++;
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
if (candidate.length > MAX_IDENTIFIER_LENGTH) {
|
|
154
|
+
funnel.rejectedTooLong++;
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
// Flags (`-d`, `--open-url`) match every other rule and are worthless to
|
|
158
|
+
// complete, so the leading character has to be a letter.
|
|
159
|
+
if (!STARTS_WITH_LETTER_REGEX.test(candidate)) {
|
|
160
|
+
funnel.rejectedNonAlphaStart++;
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
// A surface the scored path can already serve does not belong here. Harvesting
|
|
164
|
+
// it would put a candidate with no frequencies, no vector and no canonical
|
|
165
|
+
// token ids up against one that has all three.
|
|
166
|
+
var known = lookupVocabularySource(candidate);
|
|
167
|
+
if (known === 'l2') {
|
|
168
|
+
funnel.rejectedKnownL2++;
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
if (known === 'l3') {
|
|
172
|
+
funnel.rejectedKnownL3++;
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
return candidate;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/** Total mentions behind a surface, across both sources. */
|
|
179
|
+
var sightingsOf = function sightingsOf(term) {
|
|
180
|
+
return term.documentSpans + term.replyOccurrences;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Order two surfaces that are competing for the same typed prefix, best first.
|
|
185
|
+
*
|
|
186
|
+
* Sightings lead, because the number of times the session named a thing is the
|
|
187
|
+
* only quantity this module has that says anything about which one is being
|
|
188
|
+
* discussed. The alphabetical tie-break is arbitrary and chosen for being
|
|
189
|
+
* arbitrary in a stable way: on a tie there is nothing to prefer, and a rule
|
|
190
|
+
* that always resolves the same way means the same prefix produces the same
|
|
191
|
+
* ghost every time rather than one that follows harvest order.
|
|
192
|
+
*
|
|
193
|
+
* Numeric-aware so `model@v2` sorts before `model@v10` — versioned identifiers
|
|
194
|
+
* are common enough in this set to be worth not reading as strings.
|
|
195
|
+
*/
|
|
196
|
+
var byCollapseOrder = function byCollapseOrder(a, b) {
|
|
197
|
+
return sightingsOf(b) - sightingsOf(a) || a.surface.localeCompare(b.surface, 'en', {
|
|
198
|
+
numeric: true,
|
|
199
|
+
sensitivity: 'base'
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Drop the weakest terms until the set is within its cap.
|
|
205
|
+
*
|
|
206
|
+
* Least evidence first, oldest sighting to break the tie. FIFO is the local
|
|
207
|
+
* precedent (MAX_INGESTED_CONTEXT_TEXTS in the plugin) but it is the wrong
|
|
208
|
+
* policy here: the earliest-harvested term may be the one the conversation is
|
|
209
|
+
* actually about, and the sighting count is the only ranking this module has.
|
|
210
|
+
*/
|
|
211
|
+
var evictWeakestTerms = function evictWeakestTerms() {
|
|
212
|
+
if (harvested.size <= MAX_HARVESTED_TERMS) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
var ordered = Array.from(harvested.entries()).sort(function (_ref3, _ref4) {
|
|
216
|
+
var _ref5 = _slicedToArray(_ref3, 2),
|
|
217
|
+
a = _ref5[1];
|
|
218
|
+
var _ref6 = _slicedToArray(_ref4, 2),
|
|
219
|
+
b = _ref6[1];
|
|
220
|
+
return sightingsOf(a) - sightingsOf(b) || a.lastSeenAt - b.lastSeenAt;
|
|
221
|
+
});
|
|
222
|
+
var _iterator2 = _createForOfIteratorHelper(ordered.slice(0, harvested.size - MAX_HARVESTED_TERMS)),
|
|
223
|
+
_step2;
|
|
224
|
+
try {
|
|
225
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
226
|
+
var _step2$value = _slicedToArray(_step2.value, 1),
|
|
227
|
+
key = _step2$value[0];
|
|
228
|
+
harvested.delete(key);
|
|
229
|
+
evictedTerms++;
|
|
230
|
+
}
|
|
231
|
+
} catch (err) {
|
|
232
|
+
_iterator2.e(err);
|
|
233
|
+
} finally {
|
|
234
|
+
_iterator2.f();
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/** Count each admitted surface once per occurrence within a single pass. */
|
|
239
|
+
var tallySpans = function tallySpans(surfaces) {
|
|
240
|
+
var counts = new Map();
|
|
241
|
+
var _iterator3 = _createForOfIteratorHelper(surfaces),
|
|
242
|
+
_step3;
|
|
243
|
+
try {
|
|
244
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
245
|
+
var surface = _step3.value;
|
|
246
|
+
var key = surface.toLowerCase();
|
|
247
|
+
var existing = counts.get(key);
|
|
248
|
+
if (existing) {
|
|
249
|
+
existing.count++;
|
|
250
|
+
} else {
|
|
251
|
+
counts.set(key, {
|
|
252
|
+
count: 1,
|
|
253
|
+
surface: surface
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} catch (err) {
|
|
258
|
+
_iterator3.e(err);
|
|
259
|
+
} finally {
|
|
260
|
+
_iterator3.f();
|
|
261
|
+
}
|
|
262
|
+
return counts;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Harvest single-backtick spans from a markdown-ish string. Fenced regions are
|
|
267
|
+
* skipped: they hold commands and file paths, and their contents are whitespace
|
|
268
|
+
* separated anyway.
|
|
269
|
+
*/
|
|
270
|
+
export var harvestInlineCodeFromText = function harvestInlineCodeFromText(text) {
|
|
271
|
+
if (!text) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
var fencedRanges = collectFencedRanges(text);
|
|
275
|
+
var admitted = [];
|
|
276
|
+
var _iterator4 = _createForOfIteratorHelper(text.matchAll(INLINE_CODE_SPAN_REGEX)),
|
|
277
|
+
_step4;
|
|
278
|
+
try {
|
|
279
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
280
|
+
var match = _step4.value;
|
|
281
|
+
if (match.index === undefined) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
contextFunnel.spansFound++;
|
|
285
|
+
if (isInsideFence(fencedRanges, match.index)) {
|
|
286
|
+
contextFunnel.rejectedInFence++;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
var surface = admitCandidate(match[1], contextFunnel);
|
|
290
|
+
if (surface) {
|
|
291
|
+
admitted.push(surface);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} catch (err) {
|
|
295
|
+
_iterator4.e(err);
|
|
296
|
+
} finally {
|
|
297
|
+
_iterator4.f();
|
|
298
|
+
}
|
|
299
|
+
var now = performance.now();
|
|
300
|
+
var _iterator5 = _createForOfIteratorHelper(tallySpans(admitted)),
|
|
301
|
+
_step5;
|
|
302
|
+
try {
|
|
303
|
+
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
|
|
304
|
+
var _step5$value = _slicedToArray(_step5.value, 2),
|
|
305
|
+
key = _step5$value[0],
|
|
306
|
+
_step5$value$ = _step5$value[1],
|
|
307
|
+
count = _step5$value$.count,
|
|
308
|
+
_surface = _step5$value$.surface;
|
|
309
|
+
var existing = harvested.get(key);
|
|
310
|
+
if (existing) {
|
|
311
|
+
existing.replyOccurrences += count;
|
|
312
|
+
existing.lastSeenAt = now;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
harvested.set(key, {
|
|
316
|
+
displayable: _surface.length >= DISPLAYABLE_MIN_LENGTH,
|
|
317
|
+
documentSpans: 0,
|
|
318
|
+
lastSeenAt: now,
|
|
319
|
+
replyOccurrences: count,
|
|
320
|
+
surface: _surface
|
|
321
|
+
});
|
|
322
|
+
contextFunnel.accepted++;
|
|
323
|
+
}
|
|
324
|
+
} catch (err) {
|
|
325
|
+
_iterator5.e(err);
|
|
326
|
+
} finally {
|
|
327
|
+
_iterator5.f();
|
|
328
|
+
}
|
|
329
|
+
evictWeakestTerms();
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Harvest code-marked text from the live document. The backtick input rule fires
|
|
334
|
+
* on the closing tick and removes both delimiters, so a finished span is only
|
|
335
|
+
* findable through its mark. Code blocks are skipped for the same reason fenced
|
|
336
|
+
* regions are.
|
|
337
|
+
*
|
|
338
|
+
* The whole document is re-read, and what it finds replaces the previous
|
|
339
|
+
* document counts rather than adding to them.
|
|
340
|
+
*/
|
|
341
|
+
export var harvestInlineCodeFromDoc = function harvestInlineCodeFromDoc(doc) {
|
|
342
|
+
documentFunnel = emptyFunnel();
|
|
343
|
+
var admitted = [];
|
|
344
|
+
doc.descendants(function (node) {
|
|
345
|
+
if (node.type.name === 'codeBlock') {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
if (node.isText && node.text && node.marks.some(function (mark) {
|
|
349
|
+
return mark.type.name === 'code';
|
|
350
|
+
})) {
|
|
351
|
+
documentFunnel.spansFound++;
|
|
352
|
+
var surface = admitCandidate(node.text, documentFunnel);
|
|
353
|
+
if (surface) {
|
|
354
|
+
admitted.push(surface);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return true;
|
|
358
|
+
});
|
|
359
|
+
var spans = tallySpans(admitted);
|
|
360
|
+
// Surfaces the document no longer holds lose their document evidence. They
|
|
361
|
+
// stay in the set on whatever reply evidence they have.
|
|
362
|
+
var _iterator6 = _createForOfIteratorHelper(harvested),
|
|
363
|
+
_step6;
|
|
364
|
+
try {
|
|
365
|
+
for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
|
|
366
|
+
var _step6$value = _slicedToArray(_step6.value, 2),
|
|
367
|
+
key = _step6$value[0],
|
|
368
|
+
term = _step6$value[1];
|
|
369
|
+
if (!spans.has(key)) {
|
|
370
|
+
term.documentSpans = 0;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
} catch (err) {
|
|
374
|
+
_iterator6.e(err);
|
|
375
|
+
} finally {
|
|
376
|
+
_iterator6.f();
|
|
377
|
+
}
|
|
378
|
+
var now = performance.now();
|
|
379
|
+
var _iterator7 = _createForOfIteratorHelper(spans),
|
|
380
|
+
_step7;
|
|
381
|
+
try {
|
|
382
|
+
for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
|
|
383
|
+
var _step7$value = _slicedToArray(_step7.value, 2),
|
|
384
|
+
_key = _step7$value[0],
|
|
385
|
+
_step7$value$ = _step7$value[1],
|
|
386
|
+
count = _step7$value$.count,
|
|
387
|
+
surface = _step7$value$.surface;
|
|
388
|
+
var existing = harvested.get(_key);
|
|
389
|
+
if (existing) {
|
|
390
|
+
existing.documentSpans = count;
|
|
391
|
+
existing.lastSeenAt = now;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
harvested.set(_key, {
|
|
395
|
+
displayable: surface.length >= DISPLAYABLE_MIN_LENGTH,
|
|
396
|
+
documentSpans: count,
|
|
397
|
+
lastSeenAt: now,
|
|
398
|
+
replyOccurrences: 0,
|
|
399
|
+
surface: surface
|
|
400
|
+
});
|
|
401
|
+
documentFunnel.accepted++;
|
|
402
|
+
}
|
|
403
|
+
} catch (err) {
|
|
404
|
+
_iterator7.e(err);
|
|
405
|
+
} finally {
|
|
406
|
+
_iterator7.f();
|
|
407
|
+
}
|
|
408
|
+
evictWeakestTerms();
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Whether a surface still has a sighting behind it.
|
|
413
|
+
*
|
|
414
|
+
* Recurrence turned out to be the wrong thing to lean on. What separates a term
|
|
415
|
+
* worth offering from a word in prose is that someone marked it as code — the
|
|
416
|
+
* author with a code mark, or the model with backticks — and that is already
|
|
417
|
+
* true of everything in this set. The rules that keep noise out are elsewhere:
|
|
418
|
+
* the surface must be absent from L2 and L3, unique among harvested terms for
|
|
419
|
+
* the typed prefix, and on a prefix the scored path has no claim to.
|
|
420
|
+
*
|
|
421
|
+
* The case this still rejects is a withdrawn sighting: a document span the
|
|
422
|
+
* author deleted drops back to zero, and a term with no reply mention behind it
|
|
423
|
+
* stops being offered.
|
|
424
|
+
*/
|
|
425
|
+
var isDisplayEligible = function isDisplayEligible(term) {
|
|
426
|
+
return term.documentSpans + term.replyOccurrences >= MIN_OCCURRENCES;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Every harvested surface that could be shown for `typedPrefix`, best first.
|
|
431
|
+
*
|
|
432
|
+
* A surface whose remaining tail is too short to render is not a rival, it is a
|
|
433
|
+
* surface the user has finished typing. Neither is one whose sighting has been
|
|
434
|
+
* withdrawn. Both used to be counted as ambiguity and used to silence the path.
|
|
435
|
+
*/
|
|
436
|
+
var collectCandidates = function collectCandidates(typedPrefix) {
|
|
437
|
+
var needle = typedPrefix.toLowerCase();
|
|
438
|
+
var candidates = [];
|
|
439
|
+
var _iterator8 = _createForOfIteratorHelper(harvested.values()),
|
|
440
|
+
_step8;
|
|
441
|
+
try {
|
|
442
|
+
for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
|
|
443
|
+
var term = _step8.value;
|
|
444
|
+
if (!term.surface.toLowerCase().startsWith(needle)) {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (term.surface.length - typedPrefix.length < MIN_GHOST_LENGTH) {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (!isDisplayEligible(term)) {
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
candidates.push(term);
|
|
454
|
+
}
|
|
455
|
+
} catch (err) {
|
|
456
|
+
_iterator8.e(err);
|
|
457
|
+
} finally {
|
|
458
|
+
_iterator8.f();
|
|
459
|
+
}
|
|
460
|
+
return candidates.sort(byCollapseOrder);
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* The harvested surface to offer for `typedPrefix`, or null.
|
|
465
|
+
*
|
|
466
|
+
* Rivals are collapsed rather than treated as a reason to stay quiet. Silence
|
|
467
|
+
* was the original rule, on the grounds that two session surfaces sharing a
|
|
468
|
+
* prefix have nothing to separate them, and it was wrong for the case this path
|
|
469
|
+
* exists to serve: a reply that answers "what are our services" names ten of
|
|
470
|
+
* them, several share a stem, and abstaining meant the harvester went quiet on
|
|
471
|
+
* exactly the reply it was built for. Being wrong here costs one keystroke —
|
|
472
|
+
* the ghost is ignored and typing continues — and staying silent costs the
|
|
473
|
+
* feature.
|
|
474
|
+
*
|
|
475
|
+
* `collapsedBy` records which rule settled it so a surprising ghost can be
|
|
476
|
+
* explained after the fact rather than guessed at.
|
|
477
|
+
*/
|
|
478
|
+
export var findHarvestedCompletion = function findHarvestedCompletion(typedPrefix) {
|
|
479
|
+
if (typedPrefix.length < MIN_TYPED_PREFIX_LENGTH) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
var _collectCandidates = collectCandidates(typedPrefix),
|
|
483
|
+
_collectCandidates2 = _toArray(_collectCandidates),
|
|
484
|
+
winner = _collectCandidates2[0],
|
|
485
|
+
runnerUp = _collectCandidates2[1],
|
|
486
|
+
rest = _arrayLikeToArray(_collectCandidates2).slice(2);
|
|
487
|
+
if (!winner) {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
collapsedBy: !runnerUp ? 'sole-match' : sightingsOf(winner) > sightingsOf(runnerUp) ? 'sightings' : 'alphabetical',
|
|
492
|
+
documentSpans: winner.documentSpans,
|
|
493
|
+
ghostText: winner.surface.slice(typedPrefix.length),
|
|
494
|
+
replyOccurrences: winner.replyOccurrences,
|
|
495
|
+
rivalSurfaces: (runnerUp ? [runnerUp].concat(_toConsumableArray(rest)) : []).map(function (term) {
|
|
496
|
+
return term.surface;
|
|
497
|
+
}),
|
|
498
|
+
surface: winner.surface,
|
|
499
|
+
typedPrefixLength: typedPrefix.length
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
/**
|
|
503
|
+
* Read the session's harvest, optionally asking what `typedPrefix` would get.
|
|
504
|
+
*
|
|
505
|
+
* Installed as `__atlCtcDebug__.harvest()` and returned rather than logged, so
|
|
506
|
+
* the console renders it as an inspectable object and a caller can assert on it.
|
|
507
|
+
*/
|
|
508
|
+
export var inspectInlineCodeHarvest = function inspectInlineCodeHarvest(typedPrefix) {
|
|
509
|
+
return _objectSpread(_objectSpread({
|
|
510
|
+
cap: MAX_HARVESTED_TERMS,
|
|
511
|
+
evicted: evictedTerms,
|
|
512
|
+
funnels: {
|
|
513
|
+
context: _objectSpread({}, contextFunnel),
|
|
514
|
+
liveDocument: _objectSpread({}, documentFunnel)
|
|
515
|
+
}
|
|
516
|
+
}, typedPrefix === undefined ? {} : {
|
|
517
|
+
match: findHarvestedCompletion(typedPrefix)
|
|
518
|
+
}), {}, {
|
|
519
|
+
terms: Array.from(harvested.values()).sort(byCollapseOrder).map(function (term) {
|
|
520
|
+
return {
|
|
521
|
+
documentSpans: term.documentSpans,
|
|
522
|
+
longEnough: term.displayable,
|
|
523
|
+
offerable: isDisplayEligible(term),
|
|
524
|
+
replyOccurrences: term.replyOccurrences,
|
|
525
|
+
sightings: sightingsOf(term),
|
|
526
|
+
surface: term.surface
|
|
527
|
+
};
|
|
528
|
+
})
|
|
529
|
+
});
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// At module scope so the console API answers before the first keystroke, which
|
|
533
|
+
// is when someone reaching for it usually asks.
|
|
534
|
+
registerCtcHarvestInspector(inspectInlineCodeHarvest);
|
|
535
|
+
export var resetInlineCodeHarvest = function resetInlineCodeHarvest() {
|
|
536
|
+
harvested.clear();
|
|
537
|
+
contextFunnel = emptyFunnel();
|
|
538
|
+
documentFunnel = emptyFunnel();
|
|
539
|
+
evictedTerms = 0;
|
|
540
|
+
lastLoggedSignature = '';
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Print the funnel when it has moved since the last print.
|
|
545
|
+
*
|
|
546
|
+
* Deliberately reports zero-span and all-rejected passes too. Logging only on a
|
|
547
|
+
* successful harvest makes "the replies held no inline code", "every span was
|
|
548
|
+
* filtered out" and "this never ran" indistinguishable, which are the three
|
|
549
|
+
* things worth telling apart.
|
|
550
|
+
*/
|
|
551
|
+
export var logInlineCodeHarvest = function logInlineCodeHarvest(trigger) {
|
|
552
|
+
if (!isAutocompleteDebugEnabled()) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
var snapshot = inspectInlineCodeHarvest();
|
|
556
|
+
var offerable = snapshot.terms.filter(function (term) {
|
|
557
|
+
return term.offerable && term.longEnough;
|
|
558
|
+
});
|
|
559
|
+
var spansFound = contextFunnel.spansFound + documentFunnel.spansFound;
|
|
560
|
+
var signature = "".concat(spansFound, ":").concat(snapshot.terms.length, ":").concat(offerable.length);
|
|
561
|
+
if (signature === lastLoggedSignature) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
lastLoggedSignature = signature;
|
|
565
|
+
ctcTag('harvest', "".concat(trigger, " \xB7 ").concat(spansFound, " inline spans \u2192 ").concat(snapshot.terms.length, " kept, ").concat(offerable.length, " offerable").concat(evictedTerms > 0 ? ", ".concat(evictedTerms, " evicted") : '', " \xB7 __atlCtcDebug__.harvest() to inspect"), CTC_STYLES.lm);
|
|
566
|
+
if (isAutocompleteDebugVerbose()) {
|
|
567
|
+
// eslint-disable-next-line no-console
|
|
568
|
+
console.table(snapshot.funnels);
|
|
569
|
+
// eslint-disable-next-line no-console
|
|
570
|
+
console.table(snapshot.terms);
|
|
571
|
+
}
|
|
572
|
+
};
|