@atlaskit/editor-plugin-autocomplete 7.0.0 → 9.0.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 +86 -0
- package/compass.yml +2 -2
- package/dist/cjs/analytics/ufo.js +2 -1
- package/dist/cjs/pm-plugins/artifact-loader.js +135 -0
- package/dist/cjs/pm-plugins/artifacts-manifest.js +3 -0
- package/dist/cjs/pm-plugins/autocomplete-plugin.js +416 -129
- package/dist/cjs/pm-plugins/canonical-lm-scoring.js +371 -0
- package/dist/cjs/pm-plugins/debug-mode.js +90 -7
- package/dist/cjs/pm-plugins/ghost-text-decoration.js +12 -3
- package/dist/cjs/pm-plugins/local-slow-lane-client.js +1202 -453
- package/dist/cjs/pm-plugins/scoring-pipeline.js +164 -19
- package/dist/cjs/pm-plugins/slow-lane-client.js +100 -37
- package/dist/cjs/pm-plugins/text-predictor.js +1951 -273
- package/dist/es2019/analytics/ufo.js +2 -0
- package/dist/es2019/pm-plugins/artifact-loader.js +70 -0
- package/dist/es2019/pm-plugins/artifacts-manifest.js +3 -0
- package/dist/es2019/pm-plugins/autocomplete-plugin.js +425 -130
- package/dist/es2019/pm-plugins/canonical-lm-scoring.js +270 -0
- package/dist/es2019/pm-plugins/debug-mode.js +87 -7
- package/dist/es2019/pm-plugins/ghost-text-decoration.js +12 -3
- package/dist/es2019/pm-plugins/local-slow-lane-client.js +940 -326
- package/dist/es2019/pm-plugins/scoring-pipeline.js +137 -13
- package/dist/es2019/pm-plugins/slow-lane-client.js +94 -37
- package/dist/es2019/pm-plugins/text-predictor.js +1603 -199
- package/dist/esm/analytics/ufo.js +2 -1
- package/dist/esm/pm-plugins/artifact-loader.js +128 -0
- package/dist/esm/pm-plugins/artifacts-manifest.js +3 -0
- package/dist/esm/pm-plugins/autocomplete-plugin.js +419 -132
- package/dist/esm/pm-plugins/canonical-lm-scoring.js +364 -0
- package/dist/esm/pm-plugins/debug-mode.js +89 -6
- package/dist/esm/pm-plugins/ghost-text-decoration.js +12 -3
- package/dist/esm/pm-plugins/local-slow-lane-client.js +1200 -451
- package/dist/esm/pm-plugins/scoring-pipeline.js +163 -18
- package/dist/esm/pm-plugins/slow-lane-client.js +100 -37
- package/dist/esm/pm-plugins/text-predictor.js +1953 -277
- package/dist/types/analytics/ufo.d.ts +1 -0
- package/dist/types/pm-plugins/artifact-loader.d.ts +44 -0
- package/dist/types/pm-plugins/artifacts-manifest.d.ts +3 -0
- package/dist/types/pm-plugins/autocomplete-plugin.d.ts +39 -0
- package/dist/types/pm-plugins/canonical-lm-scoring.d.ts +158 -0
- package/dist/types/pm-plugins/debug-mode.d.ts +36 -1
- package/dist/types/pm-plugins/ghost-text-decoration.d.ts +5 -2
- package/dist/types/pm-plugins/local-slow-lane-client.d.ts +87 -54
- package/dist/types/pm-plugins/scoring-pipeline.d.ts +33 -1
- package/dist/types/pm-plugins/slow-lane-client.d.ts +51 -3
- package/dist/types/pm-plugins/text-predictor.d.ts +120 -2
- package/package.json +3 -3
- package/src/analytics/ufo.ts +5 -0
- package/src/pm-plugins/artifact-loader.ts +92 -0
- package/src/pm-plugins/artifacts-manifest.ts +3 -0
- package/src/pm-plugins/autocomplete-plugin.ts +517 -133
- package/src/pm-plugins/canonical-lm-scoring.ts +412 -0
- package/src/pm-plugins/debug-mode.ts +109 -7
- package/src/pm-plugins/ghost-text-decoration.ts +13 -4
- package/src/pm-plugins/local-slow-lane-client.ts +1302 -386
- package/src/pm-plugins/scoring-pipeline.ts +167 -17
- package/src/pm-plugins/slow-lane-client.ts +147 -62
- package/src/pm-plugins/text-predictor.ts +2006 -253
- package/CANONICAL_FIX__DO_NOT_USE_ME_A/package.json +0 -8
- package/CANONICAL_FIX__DO_NOT_USE_ME_B/package.json +0 -8
- package/CANONICAL_FIX__DO_NOT_USE_ME_C/package.json +0 -8
- package/dist/cjs/entry-points/src-pm-plugins-autocomplete-plugin.js +0 -18
- package/dist/cjs/entry-points/src-pm-plugins-slow-lane-client.js +0 -36
- package/dist/cjs/entry-points/src-pm-plugins-text-predictor.js +0 -66
- package/dist/es2019/entry-points/src-pm-plugins-autocomplete-plugin.js +0 -2
- package/dist/es2019/entry-points/src-pm-plugins-slow-lane-client.js +0 -2
- package/dist/es2019/entry-points/src-pm-plugins-text-predictor.js +0 -2
- package/dist/esm/entry-points/src-pm-plugins-autocomplete-plugin.js +0 -2
- package/dist/esm/entry-points/src-pm-plugins-slow-lane-client.js +0 -2
- package/dist/esm/entry-points/src-pm-plugins-text-predictor.js +0 -2
- package/dist/types/entry-points/src-pm-plugins-autocomplete-plugin.d.ts +0 -2
- package/dist/types/entry-points/src-pm-plugins-slow-lane-client.d.ts +0 -2
- package/dist/types/entry-points/src-pm-plugins-text-predictor.d.ts +0 -2
- package/scripts/gen_first_token_to_words.py +0 -170
- package/src/entry-points/src-pm-plugins-autocomplete-plugin.ts +0 -7
- package/src/entry-points/src-pm-plugins-slow-lane-client.ts +0 -13
- package/src/entry-points/src-pm-plugins-text-predictor.ts +0 -14
- package/src/pm-plugins/autocomplete-plugin/package.json +0 -8
- package/src/pm-plugins/slow-lane-client/package.json +0 -8
- package/src/pm-plugins/text-predictor/package.json +0 -8
|
@@ -4,14 +4,15 @@ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
|
4
4
|
import { keydownHandler } from '@atlaskit/editor-prosemirror/keymap';
|
|
5
5
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
6
6
|
import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
7
|
-
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
7
|
+
import { CTC_STYLES, ctcTag, isAutocompleteDebugEnabled, isAutocompleteDebugVerbose } from './debug-mode';
|
|
8
8
|
import { createGhostTextDecorationSet } from './ghost-text-decoration';
|
|
9
9
|
import { createLocalSlowLaneClient } from './local-slow-lane-client';
|
|
10
10
|
import { loadGrammarDataAsync } from './scoring-pipeline';
|
|
11
|
-
import { createSlowLaneClient,
|
|
12
|
-
import { predict, loadDefaultVocabulary, loadVectorsAsync, incrementSessionFreq, ingestDocumentPage } from './text-predictor';
|
|
11
|
+
import { clearDefaultSlowLaneClient, createSlowLaneClient, getDefaultSlowLaneClientStatus, isWordBoundary, setDefaultSlowLaneClient } from './slow-lane-client';
|
|
12
|
+
import { predict, loadDefaultVocabulary, loadVectorsAsync, incrementSessionFreq, ingestDocumentPage, getLastPredictionDebug, noteSuggestionAccepted } from './text-predictor';
|
|
13
13
|
export const autocompletePluginKey = new PluginKey('autocomplete');
|
|
14
|
-
const
|
|
14
|
+
const PREDICTION_COALESCE_MS = 0;
|
|
15
|
+
const GHOST_DECISION_BUDGET_MS = 100;
|
|
15
16
|
const NETWORK_SLOW_LANE_DEBOUNCE_MS = 300;
|
|
16
17
|
const LOCAL_SLOW_LANE_DEBOUNCE_MS = 100;
|
|
17
18
|
const CONTEXT_REFRESH_THROTTLE_MS = 1000;
|
|
@@ -26,18 +27,82 @@ const MAX_INGESTED_CONTEXT_TEXTS = 50;
|
|
|
26
27
|
// on every word boundary for the plugin's lifetime.
|
|
27
28
|
const MAX_CONTEXT_REFRESH_ATTEMPTS = 5;
|
|
28
29
|
const INLINE_LEAF_TEXT = ' ';
|
|
30
|
+
let slowLaneClientInstanceSequence = 0;
|
|
29
31
|
|
|
30
|
-
// eslint-disable-next-line require-unicode-regexp
|
|
31
|
-
const TRAILING_NON_BOUNDARY_TOKEN_REGEX = /([^\s.,;:!?]*)$/;
|
|
32
32
|
// eslint-disable-next-line require-unicode-regexp
|
|
33
33
|
const WORD_BOUNDARY_CHARS_REGEX = /[\s.,;:!?]/;
|
|
34
|
+
const TRAILING_SURFACE_PREFIX_REGEX = /[\p{L}\p{N}_'-]+$/u;
|
|
34
35
|
const hasDestroy = client => 'destroy' in client && typeof client.destroy === 'function';
|
|
35
36
|
const createInitialState = () => ({
|
|
36
37
|
ghostText: '',
|
|
37
38
|
ghostPosition: -1,
|
|
38
|
-
decorationSet: DecorationSet.empty
|
|
39
|
+
decorationSet: DecorationSet.empty,
|
|
40
|
+
suggestion: null
|
|
41
|
+
});
|
|
42
|
+
const clearGhostState = pluginState => ({
|
|
43
|
+
...pluginState,
|
|
44
|
+
ghostText: '',
|
|
45
|
+
ghostPosition: -1,
|
|
46
|
+
decorationSet: DecorationSet.empty,
|
|
47
|
+
suggestion: null
|
|
39
48
|
});
|
|
40
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Keep a displayed ghost alive when the user types the character it already
|
|
52
|
+
* predicted.
|
|
53
|
+
*
|
|
54
|
+
* Clearing on every document change throws away a suggestion the keystroke just
|
|
55
|
+
* confirmed, and the replacement decision has to re-earn it from an empty state
|
|
56
|
+
* inside the budget — which it often misses, so the ghost blinks out mid-word.
|
|
57
|
+
* It is worse for multi-word surfaces: typing the space in `machine learning`
|
|
58
|
+
* empties the current word, and the display gate refuses to show anything again
|
|
59
|
+
* until three characters of `learning` exist.
|
|
60
|
+
*
|
|
61
|
+
* A confirming keystroke is not new information about the prediction, so the
|
|
62
|
+
* ghost simply advances by one character and the still-running decision is free
|
|
63
|
+
* to replace it with something better. Anything else — a different character,
|
|
64
|
+
* a paste, a deletion, an edit elsewhere — clears as before.
|
|
65
|
+
*/
|
|
66
|
+
const advanceGhostThroughTypedCharacter = (tr, pluginState) => {
|
|
67
|
+
const {
|
|
68
|
+
ghostPosition,
|
|
69
|
+
ghostText,
|
|
70
|
+
suggestion
|
|
71
|
+
} = pluginState;
|
|
72
|
+
if (ghostText.length < 2 || ghostPosition < 0 || !suggestion) {
|
|
73
|
+
// A one-character tail leaves nothing to show after advancing.
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Exactly one character longer, with the cursor sitting just past the ghost
|
|
78
|
+
// anchor: together these identify a single insertion made at the anchor
|
|
79
|
+
// rather than a paste or an edit elsewhere in the document.
|
|
80
|
+
if (tr.doc.content.size - tr.before.content.size !== 1) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (!tr.selection.empty || tr.selection.from !== ghostPosition + 1) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (tr.doc.textBetween(ghostPosition, ghostPosition + 1) !== ghostText[0]) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const remaining = ghostText.slice(1);
|
|
90
|
+
const nextPosition = ghostPosition + 1;
|
|
91
|
+
return {
|
|
92
|
+
...pluginState,
|
|
93
|
+
ghostText: remaining,
|
|
94
|
+
ghostPosition: nextPosition,
|
|
95
|
+
decorationSet: createGhostTextDecorationSet(tr.doc, nextPosition, remaining),
|
|
96
|
+
// `surface` stays the full candidate so cooldown and analytics still
|
|
97
|
+
// describe the suggestion that was originally committed.
|
|
98
|
+
suggestion: {
|
|
99
|
+
...suggestion,
|
|
100
|
+
ghostText: remaining,
|
|
101
|
+
position: nextPosition
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
41
106
|
/**
|
|
42
107
|
* Extract text content before the cursor from the current document.
|
|
43
108
|
* Returns the last ~200 characters for context.
|
|
@@ -70,6 +135,10 @@ const getTextBeforeCursor = state => {
|
|
|
70
135
|
}
|
|
71
136
|
return fullText.slice(-maxChars);
|
|
72
137
|
};
|
|
138
|
+
const getTrailingSurfacePrefixLength = text => {
|
|
139
|
+
var _text$trimEnd$match$, _text$trimEnd$match;
|
|
140
|
+
return (_text$trimEnd$match$ = (_text$trimEnd$match = text.trimEnd().match(TRAILING_SURFACE_PREFIX_REGEX)) === null || _text$trimEnd$match === void 0 ? void 0 : _text$trimEnd$match[0].length) !== null && _text$trimEnd$match$ !== void 0 ? _text$trimEnd$match$ : 0;
|
|
141
|
+
};
|
|
73
142
|
|
|
74
143
|
/**
|
|
75
144
|
* Set the autocomplete state via a transaction metadata.
|
|
@@ -82,23 +151,41 @@ const setAutocompleteMeta = (tr, meta) => {
|
|
|
82
151
|
* Apply a ghost text suggestion to the editor state.
|
|
83
152
|
*/
|
|
84
153
|
let lastShownGhostText = '';
|
|
85
|
-
const showGhostText = (view,
|
|
154
|
+
const showGhostText = (view, prediction, position, revision, decisionStartedAt) => {
|
|
86
155
|
try {
|
|
87
156
|
const {
|
|
88
157
|
state,
|
|
89
158
|
dispatch
|
|
90
159
|
} = view;
|
|
91
|
-
const
|
|
160
|
+
const suggestion = {
|
|
161
|
+
decisionLatencyMs: performance.now() - decisionStartedAt,
|
|
162
|
+
evidenceTier: prediction.evidenceTier,
|
|
163
|
+
ghostText: prediction.text,
|
|
164
|
+
meanTokenLogProbability: prediction.meanTokenLogProbability,
|
|
165
|
+
poolHeldExtension: prediction.poolHeldExtension,
|
|
166
|
+
position,
|
|
167
|
+
posterior: prediction.posterior,
|
|
168
|
+
rankScore: prediction.rankScore,
|
|
169
|
+
revision,
|
|
170
|
+
shortlistSize: prediction.shortlistSize,
|
|
171
|
+
surface: prediction.surface,
|
|
172
|
+
termType: prediction.termType,
|
|
173
|
+
winnerMargin: prediction.winnerMargin
|
|
174
|
+
};
|
|
175
|
+
const decorationSet = createGhostTextDecorationSet(state.doc, position, prediction.text);
|
|
92
176
|
const tr = setAutocompleteMeta(state.tr, {
|
|
93
|
-
ghostText: text,
|
|
177
|
+
ghostText: prediction.text,
|
|
94
178
|
ghostPosition: position,
|
|
95
|
-
decorationSet
|
|
179
|
+
decorationSet,
|
|
180
|
+
suggestion
|
|
96
181
|
});
|
|
97
182
|
dispatch(tr);
|
|
183
|
+
return suggestion;
|
|
98
184
|
} catch (error) {
|
|
99
185
|
logException(error, {
|
|
100
186
|
location: 'editor-plugin-autocomplete/showGhostText'
|
|
101
187
|
});
|
|
188
|
+
return null;
|
|
102
189
|
}
|
|
103
190
|
};
|
|
104
191
|
|
|
@@ -115,7 +202,8 @@ const clearGhostText = (state, dispatch) => {
|
|
|
115
202
|
const tr = setAutocompleteMeta(state.tr, {
|
|
116
203
|
ghostText: '',
|
|
117
204
|
ghostPosition: -1,
|
|
118
|
-
decorationSet: DecorationSet.empty
|
|
205
|
+
decorationSet: DecorationSet.empty,
|
|
206
|
+
suggestion: null
|
|
119
207
|
});
|
|
120
208
|
dispatch(tr);
|
|
121
209
|
} catch (error) {
|
|
@@ -133,40 +221,43 @@ const clearGhostText = (state, dispatch) => {
|
|
|
133
221
|
*/
|
|
134
222
|
const acceptGhostText = (state, dispatch) => {
|
|
135
223
|
const pluginState = autocompletePluginKey.getState(state);
|
|
136
|
-
if (!pluginState || !pluginState.ghostText) {
|
|
137
|
-
return
|
|
224
|
+
if (!(pluginState !== null && pluginState !== void 0 && pluginState.suggestion) || !pluginState.ghostText) {
|
|
225
|
+
return null;
|
|
138
226
|
}
|
|
139
227
|
if (dispatch) {
|
|
140
228
|
try {
|
|
141
229
|
const {
|
|
142
230
|
ghostText,
|
|
143
|
-
ghostPosition
|
|
231
|
+
ghostPosition,
|
|
232
|
+
suggestion
|
|
144
233
|
} = pluginState;
|
|
145
234
|
let tr = state.tr.insertText(ghostText, ghostPosition);
|
|
146
235
|
tr = setAutocompleteMeta(tr, {
|
|
147
236
|
ghostText: '',
|
|
148
237
|
ghostPosition: -1,
|
|
149
|
-
decorationSet: DecorationSet.empty
|
|
238
|
+
decorationSet: DecorationSet.empty,
|
|
239
|
+
suggestion: null
|
|
150
240
|
});
|
|
151
241
|
dispatch(tr);
|
|
242
|
+
return suggestion;
|
|
152
243
|
} catch (error) {
|
|
153
244
|
logException(error, {
|
|
154
245
|
location: 'editor-plugin-autocomplete/acceptGhostText'
|
|
155
246
|
});
|
|
156
|
-
return
|
|
247
|
+
return null;
|
|
157
248
|
}
|
|
158
249
|
}
|
|
159
|
-
return
|
|
250
|
+
return pluginState.suggestion;
|
|
160
251
|
};
|
|
161
252
|
const acceptGhostTextWithAnalytics = (state, dispatch, onAccepted) => {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
onAccepted(
|
|
253
|
+
const acceptedSuggestion = acceptGhostText(state, dispatch);
|
|
254
|
+
if (acceptedSuggestion) {
|
|
255
|
+
// Start the post-accept cooldown so the just-accepted unit isn't immediately
|
|
256
|
+
// re-offered (QI-2 echo, e.g. `end to end` → `end to end to end`).
|
|
257
|
+
noteSuggestionAccepted(acceptedSuggestion.surface);
|
|
258
|
+
onAccepted(acceptedSuggestion);
|
|
168
259
|
}
|
|
169
|
-
return
|
|
260
|
+
return acceptedSuggestion !== null;
|
|
170
261
|
};
|
|
171
262
|
|
|
172
263
|
/**
|
|
@@ -228,14 +319,6 @@ const getLeadingTextCharacter = text => {
|
|
|
228
319
|
}
|
|
229
320
|
return String.fromCodePoint(firstCodePoint);
|
|
230
321
|
};
|
|
231
|
-
const getTypedLengthForPrediction = textBefore => {
|
|
232
|
-
var _textBefore$match$, _textBefore$match;
|
|
233
|
-
if (isWordBoundary(textBefore)) {
|
|
234
|
-
return 0;
|
|
235
|
-
}
|
|
236
|
-
const trailingToken = (_textBefore$match$ = (_textBefore$match = textBefore.match(TRAILING_NON_BOUNDARY_TOKEN_REGEX)) === null || _textBefore$match === void 0 ? void 0 : _textBefore$match[1]) !== null && _textBefore$match$ !== void 0 ? _textBefore$match$ : '';
|
|
237
|
-
return trailingToken.length;
|
|
238
|
-
};
|
|
239
322
|
const isEnglishLocale = locale => {
|
|
240
323
|
if (!locale) {
|
|
241
324
|
return false;
|
|
@@ -243,11 +326,12 @@ const isEnglishLocale = locale => {
|
|
|
243
326
|
return locale.toLowerCase().startsWith('en');
|
|
244
327
|
};
|
|
245
328
|
export const createAutocompletePlugin = (options, api) => {
|
|
246
|
-
var _options$surface, _options$locale;
|
|
329
|
+
var _options$surface, _options$locale, _registeredSlowLaneSt;
|
|
247
330
|
const surface = (_options$surface = options === null || options === void 0 ? void 0 : options.surface) !== null && _options$surface !== void 0 ? _options$surface : 'editor';
|
|
248
331
|
const locale = (_options$locale = options === null || options === void 0 ? void 0 : options.locale) !== null && _options$locale !== void 0 ? _options$locale : typeof navigator !== 'undefined' ? navigator.language : undefined;
|
|
249
332
|
const isAutocompleteEnabled = isEnglishLocale(locale);
|
|
250
333
|
let debounceTimer = null;
|
|
334
|
+
let decisionDeadlineTimer = null;
|
|
251
335
|
let hasIngestedPage = false;
|
|
252
336
|
let resolvedContext;
|
|
253
337
|
/**
|
|
@@ -257,6 +341,10 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
257
341
|
*/
|
|
258
342
|
let currentView = null;
|
|
259
343
|
let unsubscribeFromContextUpdates;
|
|
344
|
+
// "Prime, don't ask-and-read": semantic, Tier-A, and Tier-B results land
|
|
345
|
+
// asynchronously, often with no following keystroke. Re-run prediction when
|
|
346
|
+
// they arrive so the caches are consumed immediately.
|
|
347
|
+
let reschedulePredictionOnSlowLaneResult = null;
|
|
260
348
|
/**
|
|
261
349
|
* Set after accepting a suggestion so the next doc-change update
|
|
262
350
|
* skips scheduling a new prediction for the just-inserted text.
|
|
@@ -270,8 +358,8 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
270
358
|
* re-showing the same suggestion. Resets to null as soon as the text changes.
|
|
271
359
|
*/
|
|
272
360
|
let dismissedContext = null;
|
|
273
|
-
let
|
|
274
|
-
let
|
|
361
|
+
let decisionRevision = 0;
|
|
362
|
+
let activeDecision;
|
|
275
363
|
|
|
276
364
|
/**
|
|
277
365
|
* cold → no slow-lane vector received yet; frequency-only trie scoring
|
|
@@ -338,10 +426,10 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
338
426
|
}
|
|
339
427
|
});
|
|
340
428
|
};
|
|
341
|
-
const fireSuggestionInsertedAnalytics =
|
|
429
|
+
const fireSuggestionInsertedAnalytics = suggestion => {
|
|
342
430
|
var _api$analytics4;
|
|
343
|
-
const typedLength =
|
|
344
|
-
const suggestionLength =
|
|
431
|
+
const typedLength = Math.max(0, suggestion.surface.length - suggestion.ghostText.length);
|
|
432
|
+
const suggestionLength = suggestion.surface.length;
|
|
345
433
|
const kssDelta = suggestionLength - typedLength;
|
|
346
434
|
api === null || api === void 0 ? void 0 : (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 ? void 0 : _api$analytics4.actions.fireAnalyticsEvent({
|
|
347
435
|
action: ACTION.SUGGESTION_INSERTED,
|
|
@@ -360,13 +448,61 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
360
448
|
debounceMs: LOCAL_SLOW_LANE_DEBOUNCE_MS,
|
|
361
449
|
onLoadSuccess: fireLocalModelLoadedAnalytics,
|
|
362
450
|
onLoadError: fireLocalModelLoadFailedAnalytics,
|
|
451
|
+
// Re-run prediction for each independently scheduled model result.
|
|
452
|
+
onUpdate: () => {
|
|
453
|
+
var _reschedulePrediction;
|
|
454
|
+
return (_reschedulePrediction = reschedulePredictionOnSlowLaneResult) === null || _reschedulePrediction === void 0 ? void 0 : _reschedulePrediction({
|
|
455
|
+
kind: 'semantic'
|
|
456
|
+
});
|
|
457
|
+
},
|
|
458
|
+
onBoundaryLmUpdate: ({
|
|
459
|
+
contextKey,
|
|
460
|
+
familyKey,
|
|
461
|
+
latencyMs
|
|
462
|
+
}) => {
|
|
463
|
+
var _reschedulePrediction2;
|
|
464
|
+
return (_reschedulePrediction2 = reschedulePredictionOnSlowLaneResult) === null || _reschedulePrediction2 === void 0 ? void 0 : _reschedulePrediction2({
|
|
465
|
+
contextKey,
|
|
466
|
+
familyKey,
|
|
467
|
+
kind: 'boundary',
|
|
468
|
+
latencyMs
|
|
469
|
+
});
|
|
470
|
+
},
|
|
471
|
+
onSurfaceScoreUpdate: ({
|
|
472
|
+
contextKey,
|
|
473
|
+
familyKey,
|
|
474
|
+
latencyMs
|
|
475
|
+
}) => {
|
|
476
|
+
var _reschedulePrediction3;
|
|
477
|
+
return (_reschedulePrediction3 = reschedulePredictionOnSlowLaneResult) === null || _reschedulePrediction3 === void 0 ? void 0 : _reschedulePrediction3({
|
|
478
|
+
contextKey,
|
|
479
|
+
familyKey,
|
|
480
|
+
kind: 'surface',
|
|
481
|
+
latencyMs
|
|
482
|
+
});
|
|
483
|
+
},
|
|
363
484
|
surface
|
|
364
485
|
}) : createSlowLaneClient({
|
|
365
486
|
baseUrl: '',
|
|
366
487
|
debounceMs: NETWORK_SLOW_LANE_DEBOUNCE_MS,
|
|
488
|
+
// Re-run prediction when the BE semantic vector / LM logits land so
|
|
489
|
+
// warm-mode unigram ranking applies without waiting for a keystroke.
|
|
490
|
+
onUpdate: () => {
|
|
491
|
+
var _reschedulePrediction4;
|
|
492
|
+
return (_reschedulePrediction4 = reschedulePredictionOnSlowLaneResult) === null || _reschedulePrediction4 === void 0 ? void 0 : _reschedulePrediction4({
|
|
493
|
+
kind: 'semantic'
|
|
494
|
+
});
|
|
495
|
+
},
|
|
367
496
|
surface
|
|
368
497
|
});
|
|
369
|
-
|
|
498
|
+
const slowLaneClientKind = options !== null && options !== void 0 && options.useLocalModel ? 'localLlm' : 'server';
|
|
499
|
+
const slowLaneClientId = `${surface}:${slowLaneClientKind}:${++slowLaneClientInstanceSequence}`;
|
|
500
|
+
setDefaultSlowLaneClient(slowLaneClient, {
|
|
501
|
+
clientId: slowLaneClientId,
|
|
502
|
+
kind: slowLaneClientKind
|
|
503
|
+
});
|
|
504
|
+
const registeredSlowLaneStatus = getDefaultSlowLaneClientStatus();
|
|
505
|
+
ctcTag('init', `slow lane registered · id=${slowLaneClientId} · selected=${slowLaneClientKind} · canonical=${registeredSlowLaneStatus.canonicalScoringSupported ? 'yes' : 'no'} · ready=${(_registeredSlowLaneSt = registeredSlowLaneStatus.localModelReady) !== null && _registeredSlowLaneSt !== void 0 ? _registeredSlowLaneSt : 'n/a'}`, slowLaneClientKind === 'localLlm' ? CTC_STYLES.good : CTC_STYLES.warn);
|
|
370
506
|
let contextRequestInFlight = false;
|
|
371
507
|
let lastContextRefreshAt = 0;
|
|
372
508
|
// Bounds the word-boundary retry loop so it terminates even when the editor is
|
|
@@ -383,7 +519,7 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
383
519
|
}
|
|
384
520
|
|
|
385
521
|
// eslint-disable-next-line no-console
|
|
386
|
-
console.log('%c[
|
|
522
|
+
console.log('%c[CTC:signal]%c getContext resolved', CTC_STYLES.brand, CTC_STYLES.body, {
|
|
387
523
|
source,
|
|
388
524
|
hasParentComment: !!(context !== null && context !== void 0 && context.parentCommentContent),
|
|
389
525
|
parentCommentPreview: context === null || context === void 0 ? void 0 : (_context$parentCommen = context.parentCommentContent) === null || _context$parentCommen === void 0 ? void 0 : _context$parentCommen.slice(0, 80),
|
|
@@ -470,77 +606,239 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
470
606
|
});
|
|
471
607
|
return true;
|
|
472
608
|
};
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* Schedule a prediction after a short debounce.
|
|
476
|
-
* Tier 1 predictions are synchronous (<0.1ms) but we still debounce
|
|
477
|
-
* to avoid unnecessary work on rapid keystrokes.
|
|
478
|
-
*/
|
|
479
|
-
const schedulePrediction = view => {
|
|
609
|
+
const clearDecisionTimers = () => {
|
|
480
610
|
if (debounceTimer) {
|
|
481
611
|
clearTimeout(debounceTimer);
|
|
612
|
+
debounceTimer = null;
|
|
482
613
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
614
|
+
if (decisionDeadlineTimer) {
|
|
615
|
+
clearTimeout(decisionDeadlineTimer);
|
|
616
|
+
decisionDeadlineTimer = null;
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
const cancelActiveDecision = () => {
|
|
620
|
+
clearDecisionTimers();
|
|
621
|
+
activeDecision = undefined;
|
|
622
|
+
};
|
|
623
|
+
const isDecisionSnapshotCurrent = (view, decision) => {
|
|
624
|
+
const {
|
|
625
|
+
state
|
|
626
|
+
} = view;
|
|
627
|
+
return state.selection.empty && state.selection.from === decision.position && getTextBeforeCursor(state) === decision.textBefore;
|
|
628
|
+
};
|
|
629
|
+
const isDecisionCurrent = (view, decision) => {
|
|
630
|
+
var _activeDecision;
|
|
631
|
+
return ((_activeDecision = activeDecision) === null || _activeDecision === void 0 ? void 0 : _activeDecision.revision) === decision.revision && isDecisionSnapshotCurrent(view, decision);
|
|
632
|
+
};
|
|
633
|
+
const expireActiveDecision = decision => {
|
|
634
|
+
var _activeDecision2;
|
|
635
|
+
if (((_activeDecision2 = activeDecision) === null || _activeDecision2 === void 0 ? void 0 : _activeDecision2.revision) !== decision.revision) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
cancelActiveDecision();
|
|
639
|
+
};
|
|
640
|
+
const evaluateActiveDecision = (view, revision) => {
|
|
641
|
+
const decision = activeDecision;
|
|
642
|
+
if (!decision || decision.revision !== revision || !isDecisionCurrent(view, decision)) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (performance.now() > decision.deadlineAt) {
|
|
646
|
+
expireActiveDecision(decision);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
decision.evaluations++;
|
|
650
|
+
try {
|
|
651
|
+
const {
|
|
652
|
+
state
|
|
653
|
+
} = view;
|
|
654
|
+
const {
|
|
655
|
+
selection
|
|
656
|
+
} = state;
|
|
494
657
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
658
|
+
// Suppress suggestions when the cursor is mid-word — only offer
|
|
659
|
+
// completions when the cursor is at the trailing edge of a token.
|
|
660
|
+
const nodeAfter = selection.$from.nodeAfter;
|
|
661
|
+
const charAfterCursor = nodeAfter !== null && nodeAfter !== void 0 && nodeAfter.isText ? getLeadingTextCharacter(nodeAfter.text) : undefined;
|
|
662
|
+
if (charAfterCursor && isMidWordCharacter(charAfterCursor)) {
|
|
663
|
+
cancelActiveDecision();
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (decision.textBefore === dismissedContext) {
|
|
667
|
+
cancelActiveDecision();
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
dismissedContext = null;
|
|
508
671
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
672
|
+
// Two characters are enough to prefetch the canonical shortlist. The
|
|
673
|
+
// predictor itself withholds display until the three-character gate.
|
|
674
|
+
if (decision.textBefore.trim().length < 2) {
|
|
675
|
+
cancelActiveDecision();
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const prediction = predict(decision.textBefore);
|
|
679
|
+
if (!prediction || prediction.text.length === 0) {
|
|
680
|
+
// Remain in `collecting` until an async signal arrives or the hard
|
|
681
|
+
// deadline expires. We never display a provisional fallback.
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
const readyAtMs = performance.now() - decision.startedAt;
|
|
685
|
+
if (readyAtMs > GHOST_DECISION_BUDGET_MS) {
|
|
686
|
+
cancelActiveDecision();
|
|
687
|
+
if (isAutocompleteDebugEnabled()) {
|
|
688
|
+
// eslint-disable-next-line no-console
|
|
689
|
+
console.log('%c[CTC:budget]%c fully gated after render deadline (render suppressed)', CTC_STYLES.section, CTC_STYLES.body, {
|
|
690
|
+
revision: decision.revision,
|
|
691
|
+
readyAtMs,
|
|
692
|
+
surface: prediction.surface
|
|
693
|
+
});
|
|
517
694
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const suggestion = showGhostText(view, prediction, decision.position, decision.revision, decision.startedAt);
|
|
698
|
+
if (!suggestion) {
|
|
699
|
+
cancelActiveDecision();
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
cancelActiveDecision();
|
|
703
|
+
if (isAutocompleteDebugEnabled()) {
|
|
704
|
+
// eslint-disable-next-line no-console
|
|
705
|
+
console.log('%c[CTC:decision]%c committed immutable ghost', CTC_STYLES.brand, CTC_STYLES.body, {
|
|
706
|
+
revision: suggestion.revision,
|
|
707
|
+
surface: suggestion.surface,
|
|
708
|
+
ghostText: suggestion.ghostText,
|
|
709
|
+
evidenceTier: suggestion.evidenceTier,
|
|
710
|
+
rankScore: suggestion.rankScore,
|
|
711
|
+
winnerMargin: suggestion.winnerMargin,
|
|
712
|
+
decisionLatencyMs: suggestion.decisionLatencyMs,
|
|
713
|
+
budgetMs: GHOST_DECISION_BUDGET_MS,
|
|
714
|
+
typedPrefixLength: getTrailingSurfacePrefixLength(decision.textBefore),
|
|
715
|
+
evaluations: decision.evaluations,
|
|
716
|
+
asyncSignals: decision.asyncSignals,
|
|
717
|
+
readyWithinBudget: suggestion.decisionLatencyMs <= GHOST_DECISION_BUDGET_MS
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
if (suggestion.ghostText !== lastShownGhostText) {
|
|
721
|
+
var _api$analytics5;
|
|
722
|
+
lastShownGhostText = suggestion.ghostText;
|
|
723
|
+
api === null || api === void 0 ? void 0 : (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 ? void 0 : _api$analytics5.actions.fireAnalyticsEvent({
|
|
724
|
+
action: ACTION.SUGGESTION_VIEWED,
|
|
725
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
726
|
+
eventType: EVENT_TYPE.TRACK,
|
|
727
|
+
attributes: {
|
|
728
|
+
completionSource: getCompletionSource(),
|
|
729
|
+
surface
|
|
536
730
|
}
|
|
537
|
-
}
|
|
538
|
-
} catch (error) {
|
|
539
|
-
logException(error, {
|
|
540
|
-
location: 'editor-plugin-autocomplete/schedulePrediction'
|
|
541
731
|
});
|
|
542
732
|
}
|
|
543
|
-
}
|
|
733
|
+
} catch (error) {
|
|
734
|
+
cancelActiveDecision();
|
|
735
|
+
logException(error, {
|
|
736
|
+
location: 'editor-plugin-autocomplete/evaluateActiveDecision'
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
const beginPredictionDecision = view => {
|
|
741
|
+
cancelActiveDecision();
|
|
742
|
+
const {
|
|
743
|
+
state
|
|
744
|
+
} = view;
|
|
745
|
+
if (!state.selection.empty) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
const startedAt = performance.now();
|
|
749
|
+
const textBefore = getTextBeforeCursor(state);
|
|
750
|
+
const revision = ++decisionRevision;
|
|
751
|
+
activeDecision = {
|
|
752
|
+
asyncSignals: 0,
|
|
753
|
+
deadlineAt: startedAt + GHOST_DECISION_BUDGET_MS,
|
|
754
|
+
evaluations: 0,
|
|
755
|
+
position: state.selection.from,
|
|
756
|
+
revision,
|
|
757
|
+
startedAt,
|
|
758
|
+
textBefore
|
|
759
|
+
};
|
|
760
|
+
if (isAutocompleteDebugEnabled()) {
|
|
761
|
+
// eslint-disable-next-line no-console
|
|
762
|
+
console.log('%c[CTC:decision]%c collecting', CTC_STYLES.brand, CTC_STYLES.body, {
|
|
763
|
+
revision,
|
|
764
|
+
budgetMs: GHOST_DECISION_BUDGET_MS,
|
|
765
|
+
coalesceMs: PREDICTION_COALESCE_MS,
|
|
766
|
+
typedPrefixLength: getTrailingSurfacePrefixLength(activeDecision.textBefore),
|
|
767
|
+
textBefore: activeDecision.textBefore.slice(-80)
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
debounceTimer = setTimeout(() => {
|
|
771
|
+
debounceTimer = null;
|
|
772
|
+
evaluateActiveDecision(view, revision);
|
|
773
|
+
}, PREDICTION_COALESCE_MS);
|
|
774
|
+
decisionDeadlineTimer = setTimeout(() => {
|
|
775
|
+
var _activeDecision3;
|
|
776
|
+
decisionDeadlineTimer = null;
|
|
777
|
+
if (((_activeDecision3 = activeDecision) === null || _activeDecision3 === void 0 ? void 0 : _activeDecision3.revision) === revision) {
|
|
778
|
+
const expiredDecision = activeDecision;
|
|
779
|
+
if (isAutocompleteDebugEnabled()) {
|
|
780
|
+
const elapsedMs = performance.now() - expiredDecision.startedAt;
|
|
781
|
+
const predictionDebug = getLastPredictionDebug();
|
|
782
|
+
const matchingPredictionDebug = (predictionDebug === null || predictionDebug === void 0 ? void 0 : predictionDebug.textBefore) === expiredDecision.textBefore ? predictionDebug : null;
|
|
783
|
+
const abstentionPrefix = '— abstain: ';
|
|
784
|
+
const predictorDecision = matchingPredictionDebug === null || matchingPredictionDebug === void 0 ? void 0 : matchingPredictionDebug.decision;
|
|
785
|
+
const predictorReason = predictorDecision !== null && predictorDecision !== void 0 && predictorDecision.startsWith(abstentionPrefix) ? predictorDecision.slice(abstentionPrefix.length) : predictorDecision;
|
|
786
|
+
const decisionMessage = predictorReason && !(matchingPredictionDebug !== null && matchingPredictionDebug !== void 0 && matchingPredictionDebug.awaitingAsyncEvidence) ? `abstain: ${predictorReason}` : `abstain: ${GHOST_DECISION_BUDGET_MS}ms decision budget expired${predictorReason ? ` · ${predictorReason}` : ''}`;
|
|
787
|
+
// eslint-disable-next-line no-console
|
|
788
|
+
console.log(`%c[CTC:decision]%c ${decisionMessage}`, CTC_STYLES.brand, CTC_STYLES.body, {
|
|
789
|
+
revision,
|
|
790
|
+
budgetMs: GHOST_DECISION_BUDGET_MS,
|
|
791
|
+
elapsedMs,
|
|
792
|
+
typedPrefixLength: getTrailingSurfacePrefixLength(expiredDecision.textBefore),
|
|
793
|
+
evaluations: expiredDecision.evaluations,
|
|
794
|
+
asyncSignals: expiredDecision.asyncSignals,
|
|
795
|
+
textBefore: expiredDecision.textBefore.slice(-80),
|
|
796
|
+
predictorDecision: predictorReason !== null && predictorReason !== void 0 ? predictorReason : null
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
expireActiveDecision(expiredDecision);
|
|
800
|
+
}
|
|
801
|
+
}, GHOST_DECISION_BUDGET_MS);
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
// Async semantic/canonical results may complete the active decision, but they
|
|
805
|
+
// never create a new decision or replace an already committed ghost.
|
|
806
|
+
reschedulePredictionOnSlowLaneResult = signal => {
|
|
807
|
+
if (destroyed || !currentView) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
if (!activeDecision) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const revision = activeDecision.revision;
|
|
814
|
+
const elapsedMs = performance.now() - activeDecision.startedAt;
|
|
815
|
+
const remainingMs = activeDecision.deadlineAt - performance.now();
|
|
816
|
+
if (remainingMs < 0) {
|
|
817
|
+
const expiredDecision = activeDecision;
|
|
818
|
+
expireActiveDecision(expiredDecision);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
activeDecision.asyncSignals++;
|
|
822
|
+
if (isAutocompleteDebugVerbose()) {
|
|
823
|
+
// eslint-disable-next-line no-console
|
|
824
|
+
console.log('%c[CTC:readiness]%c async evidence signal', CTC_STYLES.section, CTC_STYLES.body, {
|
|
825
|
+
...signal,
|
|
826
|
+
revision,
|
|
827
|
+
elapsedMs,
|
|
828
|
+
remainingMs: Math.max(0, remainingMs),
|
|
829
|
+
typedPrefixLength: getTrailingSurfacePrefixLength(activeDecision.textBefore),
|
|
830
|
+
withinBudget: remainingMs >= 0
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
if (debounceTimer) {
|
|
834
|
+
clearTimeout(debounceTimer);
|
|
835
|
+
}
|
|
836
|
+
debounceTimer = setTimeout(() => {
|
|
837
|
+
debounceTimer = null;
|
|
838
|
+
if (currentView) {
|
|
839
|
+
evaluateActiveDecision(currentView, revision);
|
|
840
|
+
}
|
|
841
|
+
}, 0);
|
|
544
842
|
};
|
|
545
843
|
const maybeUpdateSessionFrequency = (view, prevState) => {
|
|
546
844
|
const newText = getTextBeforeCursor(view.state);
|
|
@@ -579,22 +877,14 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
579
877
|
};
|
|
580
878
|
}
|
|
581
879
|
|
|
582
|
-
// A new prediction is scheduled from view.update
|
|
880
|
+
// A new prediction is scheduled from view.update either way; the
|
|
881
|
+
// surviving ghost only covers the gap until it commits.
|
|
583
882
|
if (tr.docChanged) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
ghostText: '',
|
|
587
|
-
ghostPosition: -1,
|
|
588
|
-
decorationSet: DecorationSet.empty
|
|
589
|
-
};
|
|
883
|
+
var _advanceGhostThroughT;
|
|
884
|
+
return (_advanceGhostThroughT = advanceGhostThroughTypedCharacter(tr, pluginState)) !== null && _advanceGhostThroughT !== void 0 ? _advanceGhostThroughT : clearGhostState(pluginState);
|
|
590
885
|
}
|
|
591
886
|
if (tr.selectionSet && pluginState.ghostText) {
|
|
592
|
-
return
|
|
593
|
-
...pluginState,
|
|
594
|
-
ghostText: '',
|
|
595
|
-
ghostPosition: -1,
|
|
596
|
-
decorationSet: DecorationSet.empty
|
|
597
|
-
};
|
|
887
|
+
return clearGhostState(pluginState);
|
|
598
888
|
}
|
|
599
889
|
return pluginState;
|
|
600
890
|
}
|
|
@@ -607,22 +897,25 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
607
897
|
},
|
|
608
898
|
handleKeyDown: keydownHandler({
|
|
609
899
|
Tab: (state, dispatch) => {
|
|
610
|
-
return acceptGhostTextWithAnalytics(state, dispatch,
|
|
900
|
+
return acceptGhostTextWithAnalytics(state, dispatch, suggestion => {
|
|
611
901
|
justAccepted = true;
|
|
612
902
|
lastShownGhostText = '';
|
|
613
|
-
|
|
903
|
+
cancelActiveDecision();
|
|
904
|
+
fireSuggestionInsertedAnalytics(suggestion);
|
|
614
905
|
});
|
|
615
906
|
},
|
|
616
907
|
ArrowRight: (state, dispatch) => {
|
|
617
|
-
return acceptGhostTextWithAnalytics(state, dispatch,
|
|
908
|
+
return acceptGhostTextWithAnalytics(state, dispatch, suggestion => {
|
|
618
909
|
justAccepted = true;
|
|
619
910
|
lastShownGhostText = '';
|
|
620
|
-
|
|
911
|
+
cancelActiveDecision();
|
|
912
|
+
fireSuggestionInsertedAnalytics(suggestion);
|
|
621
913
|
});
|
|
622
914
|
},
|
|
623
915
|
Escape: (state, dispatch) => {
|
|
624
916
|
const didClear = clearGhostText(state, dispatch);
|
|
625
917
|
if (didClear) {
|
|
918
|
+
cancelActiveDecision();
|
|
626
919
|
dismissedContext = getTextBeforeCursor(state);
|
|
627
920
|
fireSuggestionDismissedAnalytics('escape');
|
|
628
921
|
}
|
|
@@ -637,6 +930,7 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
637
930
|
const pluginState = autocompletePluginKey.getState(view.state);
|
|
638
931
|
if (pluginState !== null && pluginState !== void 0 && pluginState.ghostText) {
|
|
639
932
|
clearGhostText(view.state, view.dispatch);
|
|
933
|
+
cancelActiveDecision();
|
|
640
934
|
fireSuggestionDismissedAnalytics('blur');
|
|
641
935
|
}
|
|
642
936
|
return false;
|
|
@@ -648,6 +942,7 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
648
942
|
if (!event.target.closest('[data-autocomplete-ghost="true"]')) {
|
|
649
943
|
const didClear = clearGhostText(view.state, view.dispatch);
|
|
650
944
|
if (didClear) {
|
|
945
|
+
cancelActiveDecision();
|
|
651
946
|
dismissedContext = getTextBeforeCursor(view.state);
|
|
652
947
|
lastShownGhostText = '';
|
|
653
948
|
fireSuggestionDismissedAnalytics('click');
|
|
@@ -655,10 +950,11 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
655
950
|
return false;
|
|
656
951
|
}
|
|
657
952
|
event.preventDefault();
|
|
658
|
-
const accepted = acceptGhostTextWithAnalytics(view.state, view.dispatch,
|
|
953
|
+
const accepted = acceptGhostTextWithAnalytics(view.state, view.dispatch, suggestion => {
|
|
659
954
|
justAccepted = true;
|
|
660
955
|
lastShownGhostText = '';
|
|
661
|
-
|
|
956
|
+
cancelActiveDecision();
|
|
957
|
+
fireSuggestionInsertedAnalytics(suggestion);
|
|
662
958
|
});
|
|
663
959
|
if (accepted) {
|
|
664
960
|
view.focus();
|
|
@@ -733,9 +1029,7 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
733
1029
|
// Snapshot the post-acceptance text so follow-up transactions hit
|
|
734
1030
|
// the dismissedContext guard and abort until the user types again.
|
|
735
1031
|
dismissedContext = getTextBeforeCursor(view.state);
|
|
736
|
-
|
|
737
|
-
clearTimeout(debounceTimer);
|
|
738
|
-
}
|
|
1032
|
+
cancelActiveDecision();
|
|
739
1033
|
return;
|
|
740
1034
|
}
|
|
741
1035
|
maybeUpdateSessionFrequency(view, prevState);
|
|
@@ -760,7 +1054,9 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
760
1054
|
}
|
|
761
1055
|
}
|
|
762
1056
|
}
|
|
763
|
-
|
|
1057
|
+
beginPredictionDecision(view);
|
|
1058
|
+
} else if (!prevState.selection.eq(view.state.selection)) {
|
|
1059
|
+
cancelActiveDecision();
|
|
764
1060
|
}
|
|
765
1061
|
},
|
|
766
1062
|
destroy: () => {
|
|
@@ -769,14 +1065,13 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
769
1065
|
currentView = null;
|
|
770
1066
|
(_unsubscribeFromConte = unsubscribeFromContextUpdates) === null || _unsubscribeFromConte === void 0 ? void 0 : _unsubscribeFromConte();
|
|
771
1067
|
unsubscribeFromContextUpdates = undefined;
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
}
|
|
1068
|
+
cancelActiveDecision();
|
|
1069
|
+
const releasedDefaultClient = clearDefaultSlowLaneClient(slowLaneClient);
|
|
1070
|
+
ctcTag('init', `slow lane teardown · id=${slowLaneClientId} · default=${releasedDefaultClient ? 'released' : 'retained newer owner'}`, releasedDefaultClient ? CTC_STYLES.dim : CTC_STYLES.warn);
|
|
775
1071
|
if (hasDestroy(slowLaneClient)) {
|
|
776
1072
|
slowLaneClient.destroy();
|
|
777
1073
|
}
|
|
778
1074
|
ingestedContextTexts.clear();
|
|
779
|
-
setDefaultSlowLaneClient(null);
|
|
780
1075
|
}
|
|
781
1076
|
};
|
|
782
1077
|
}
|