@difizen/libro-codemirror 0.0.2-alpha.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.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/es/auto-complete/closebrackets.d.ts +12 -0
  4. package/es/auto-complete/closebrackets.d.ts.map +1 -0
  5. package/es/auto-complete/closebrackets.js +408 -0
  6. package/es/auto-complete/completion.d.ts +57 -0
  7. package/es/auto-complete/completion.d.ts.map +1 -0
  8. package/es/auto-complete/completion.js +265 -0
  9. package/es/auto-complete/config.d.ts +22 -0
  10. package/es/auto-complete/config.d.ts.map +1 -0
  11. package/es/auto-complete/config.js +44 -0
  12. package/es/auto-complete/filter.d.ts +13 -0
  13. package/es/auto-complete/filter.d.ts.map +1 -0
  14. package/es/auto-complete/filter.js +191 -0
  15. package/es/auto-complete/index.d.ts +17 -0
  16. package/es/auto-complete/index.d.ts.map +1 -0
  17. package/es/auto-complete/index.js +107 -0
  18. package/es/auto-complete/snippet.d.ts +14 -0
  19. package/es/auto-complete/snippet.d.ts.map +1 -0
  20. package/es/auto-complete/snippet.js +447 -0
  21. package/es/auto-complete/state.d.ts +63 -0
  22. package/es/auto-complete/state.d.ts.map +1 -0
  23. package/es/auto-complete/state.js +452 -0
  24. package/es/auto-complete/theme.d.ts +6 -0
  25. package/es/auto-complete/theme.d.ts.map +1 -0
  26. package/es/auto-complete/theme.js +151 -0
  27. package/es/auto-complete/tooltip.d.ts +5 -0
  28. package/es/auto-complete/tooltip.d.ts.map +1 -0
  29. package/es/auto-complete/tooltip.js +365 -0
  30. package/es/auto-complete/view.d.ts +43 -0
  31. package/es/auto-complete/view.d.ts.map +1 -0
  32. package/es/auto-complete/view.js +372 -0
  33. package/es/auto-complete/word.d.ts +3 -0
  34. package/es/auto-complete/word.d.ts.map +1 -0
  35. package/es/auto-complete/word.js +119 -0
  36. package/es/completion.d.ts +6 -0
  37. package/es/completion.d.ts.map +1 -0
  38. package/es/completion.js +84 -0
  39. package/es/config.d.ts +184 -0
  40. package/es/config.d.ts.map +1 -0
  41. package/es/config.js +473 -0
  42. package/es/editor.d.ts +361 -0
  43. package/es/editor.d.ts.map +1 -0
  44. package/es/editor.js +1126 -0
  45. package/es/factory.d.ts +3 -0
  46. package/es/factory.d.ts.map +1 -0
  47. package/es/factory.js +12 -0
  48. package/es/hyperlink.d.ts +15 -0
  49. package/es/hyperlink.d.ts.map +1 -0
  50. package/es/hyperlink.js +120 -0
  51. package/es/indent.d.ts +8 -0
  52. package/es/indent.d.ts.map +1 -0
  53. package/es/indent.js +58 -0
  54. package/es/indentation-markers/config.d.ts +17 -0
  55. package/es/indentation-markers/config.d.ts.map +1 -0
  56. package/es/indentation-markers/config.js +10 -0
  57. package/es/indentation-markers/index.d.ts +3 -0
  58. package/es/indentation-markers/index.d.ts.map +1 -0
  59. package/es/indentation-markers/index.js +160 -0
  60. package/es/indentation-markers/map.d.ts +77 -0
  61. package/es/indentation-markers/map.d.ts.map +1 -0
  62. package/es/indentation-markers/map.js +265 -0
  63. package/es/indentation-markers/utils.d.ts +27 -0
  64. package/es/indentation-markers/utils.d.ts.map +1 -0
  65. package/es/indentation-markers/utils.js +91 -0
  66. package/es/index.d.ts +11 -0
  67. package/es/index.d.ts.map +1 -0
  68. package/es/index.js +10 -0
  69. package/es/libro-icon.d.ts +3 -0
  70. package/es/libro-icon.d.ts.map +1 -0
  71. package/es/libro-icon.js +2 -0
  72. package/es/mimetype.d.ts +22 -0
  73. package/es/mimetype.d.ts.map +1 -0
  74. package/es/mimetype.js +59 -0
  75. package/es/mode.d.ts +86 -0
  76. package/es/mode.d.ts.map +1 -0
  77. package/es/mode.js +284 -0
  78. package/es/monitor.d.ts +32 -0
  79. package/es/monitor.d.ts.map +1 -0
  80. package/es/monitor.js +129 -0
  81. package/es/python-lang.d.ts +3 -0
  82. package/es/python-lang.d.ts.map +1 -0
  83. package/es/python-lang.js +7 -0
  84. package/es/style/base.css +131 -0
  85. package/es/style/theme.css +12 -0
  86. package/es/style/variables.css +403 -0
  87. package/es/theme.d.ts +35 -0
  88. package/es/theme.d.ts.map +1 -0
  89. package/es/theme.js +225 -0
  90. package/es/tooltip.d.ts +10 -0
  91. package/es/tooltip.d.ts.map +1 -0
  92. package/es/tooltip.js +170 -0
  93. package/package.json +74 -0
  94. package/src/auto-complete/README.md +71 -0
  95. package/src/auto-complete/closebrackets.ts +423 -0
  96. package/src/auto-complete/completion.ts +345 -0
  97. package/src/auto-complete/config.ts +101 -0
  98. package/src/auto-complete/filter.ts +215 -0
  99. package/src/auto-complete/index.ts +112 -0
  100. package/src/auto-complete/snippet.ts +394 -0
  101. package/src/auto-complete/state.ts +472 -0
  102. package/src/auto-complete/theme.ts +126 -0
  103. package/src/auto-complete/tooltip.ts +386 -0
  104. package/src/auto-complete/view.ts +343 -0
  105. package/src/auto-complete/word.ts +118 -0
  106. package/src/completion.ts +61 -0
  107. package/src/config.ts +689 -0
  108. package/src/editor.ts +1078 -0
  109. package/src/factory.ts +10 -0
  110. package/src/hyperlink.ts +95 -0
  111. package/src/indent.ts +69 -0
  112. package/src/indentation-markers/config.ts +31 -0
  113. package/src/indentation-markers/index.ts +192 -0
  114. package/src/indentation-markers/map.ts +273 -0
  115. package/src/indentation-markers/utils.ts +84 -0
  116. package/src/index.ts +11 -0
  117. package/src/libro-icon.ts +4 -0
  118. package/src/mimetype.ts +49 -0
  119. package/src/mode.ts +269 -0
  120. package/src/monitor.ts +105 -0
  121. package/src/python-lang.ts +7 -0
  122. package/src/style/base.css +129 -0
  123. package/src/style/theme.css +12 -0
  124. package/src/style/variables.css +405 -0
  125. package/src/theme.ts +231 -0
  126. package/src/tooltip.ts +145 -0
@@ -0,0 +1,343 @@
1
+ /* eslint-disable @typescript-eslint/no-parameter-properties */
2
+ /* eslint-disable @typescript-eslint/parameter-properties */
3
+ import type { Transaction } from '@codemirror/state';
4
+ import type {
5
+ EditorView,
6
+ Command,
7
+ PluginValue,
8
+ ViewUpdate,
9
+ TooltipView,
10
+ } from '@codemirror/view';
11
+ import { ViewPlugin, logException, getTooltip } from '@codemirror/view';
12
+
13
+ import type { CompletionResult } from './completion.js';
14
+ import { cur, CompletionContext, applyCompletion } from './completion.js';
15
+ import { completionConfig } from './config.js';
16
+ import {
17
+ completionState,
18
+ setSelectedEffect,
19
+ startCompletionEffect,
20
+ closeCompletionEffect,
21
+ setActiveEffect,
22
+ State,
23
+ ActiveSource,
24
+ ActiveResult,
25
+ getUserEvent,
26
+ } from './state.js';
27
+
28
+ /// Returns a command that moves the completion selection forward or
29
+ /// backward by the given amount.
30
+ export function moveCompletionSelection(
31
+ forward: boolean,
32
+ by: 'option' | 'page' = 'option',
33
+ ): Command {
34
+ return (view: EditorView) => {
35
+ const cState = view.state.field(completionState, false);
36
+ if (
37
+ !cState ||
38
+ !cState.open ||
39
+ Date.now() - cState.open.timestamp <
40
+ view.state.facet(completionConfig).interactionDelay
41
+ ) {
42
+ return false;
43
+ }
44
+ let step = 1,
45
+ tooltip: TooltipView | null;
46
+ if (by === 'page' && (tooltip = getTooltip(view, cState.open.tooltip))) {
47
+ step = Math.max(
48
+ 2,
49
+ Math.floor(
50
+ tooltip.dom.offsetHeight /
51
+ (tooltip.dom.querySelector('li') as HTMLElement).offsetHeight,
52
+ ) - 1,
53
+ );
54
+ }
55
+ const { length } = cState.open.options;
56
+ let selected =
57
+ cState.open.selected > -1
58
+ ? cState.open.selected + step * (forward ? 1 : -1)
59
+ : forward
60
+ ? 0
61
+ : length - 1;
62
+ if (selected < 0) {
63
+ selected = by === 'page' ? 0 : length - 1;
64
+ } else if (selected >= length) {
65
+ selected = by === 'page' ? length - 1 : 0;
66
+ }
67
+ view.dispatch({ effects: setSelectedEffect.of(selected) });
68
+ return true;
69
+ };
70
+ }
71
+
72
+ /// Accept the current completion.
73
+ export const acceptCompletion: Command = (view: EditorView) => {
74
+ const cState = view.state.field(completionState, false);
75
+ if (
76
+ view.state.readOnly ||
77
+ !cState ||
78
+ !cState.open ||
79
+ cState.open.selected < 0 ||
80
+ Date.now() - cState.open.timestamp <
81
+ view.state.facet(completionConfig).interactionDelay
82
+ ) {
83
+ return false;
84
+ }
85
+ applyCompletion(view, cState.open.options[cState.open.selected]);
86
+ return true;
87
+ };
88
+
89
+ /// Explicitly start autocompletion.
90
+ export const startCompletion: Command = (view: EditorView) => {
91
+ const cState = view.state.field(completionState, false);
92
+ if (!cState) {
93
+ return false;
94
+ }
95
+ view.dispatch({ effects: startCompletionEffect.of(true) });
96
+ return true;
97
+ };
98
+
99
+ /// Close the currently active completion.
100
+ export const closeCompletion: Command = (view: EditorView) => {
101
+ const cState = view.state.field(completionState, false);
102
+ if (!cState || !cState.active.some((a) => a.state !== State.Inactive)) {
103
+ return false;
104
+ }
105
+ view.dispatch({ effects: closeCompletionEffect.of(null) });
106
+ return true;
107
+ };
108
+
109
+ class RunningQuery {
110
+ time = Date.now();
111
+ updates: Transaction[] = [];
112
+ // Note that 'undefined' means 'not done yet', whereas 'null' means
113
+ // 'query returned null'.
114
+ done: undefined | CompletionResult | null = undefined;
115
+
116
+ constructor(
117
+ readonly active: ActiveSource,
118
+ readonly context: CompletionContext,
119
+ ) {}
120
+ }
121
+
122
+ const DebounceTime = 50,
123
+ MaxUpdateCount = 50,
124
+ MinAbortTime = 1000;
125
+
126
+ const enum CompositionState {
127
+ None,
128
+ Started,
129
+ Changed,
130
+ ChangedAndMoved,
131
+ }
132
+
133
+ export const completionPlugin = ViewPlugin.fromClass(
134
+ class implements PluginValue {
135
+ debounceUpdate: NodeJS.Timeout | -1 = -1;
136
+ running: RunningQuery[] = [];
137
+ debounceAccept: NodeJS.Timeout | -1 = -1;
138
+ composing = CompositionState.None;
139
+
140
+ constructor(readonly view: EditorView) {
141
+ for (const active of view.state.field(completionState).active) {
142
+ if (active.state === State.Pending) {
143
+ this.startQuery(active);
144
+ }
145
+ }
146
+ }
147
+
148
+ update(update: ViewUpdate) {
149
+ const cState = update.state.field(completionState);
150
+ if (
151
+ !update.selectionSet &&
152
+ !update.docChanged &&
153
+ update.startState.field(completionState) === cState
154
+ ) {
155
+ return;
156
+ }
157
+
158
+ const doesReset = update.transactions.some((tr) => {
159
+ return (tr.selection || tr.docChanged) && !getUserEvent(tr);
160
+ });
161
+ for (let i = 0; i < this.running.length; i++) {
162
+ const query = this.running[i];
163
+ if (
164
+ doesReset ||
165
+ (query.updates.length + update.transactions.length > MaxUpdateCount &&
166
+ Date.now() - query.time > MinAbortTime)
167
+ ) {
168
+ for (const handler of query.context.abortListeners!) {
169
+ try {
170
+ handler();
171
+ } catch (e) {
172
+ logException(this.view.state, e);
173
+ }
174
+ }
175
+ query.context.abortListeners = null;
176
+ this.running.splice(i--, 1);
177
+ } else {
178
+ query.updates.push(...update.transactions);
179
+ }
180
+ }
181
+
182
+ if (this.debounceUpdate > -1) {
183
+ clearTimeout(this.debounceUpdate);
184
+ }
185
+ this.debounceUpdate = cState.active.some(
186
+ (a) =>
187
+ a.state === State.Pending &&
188
+ !this.running.some((q) => q.active.source === a.source),
189
+ )
190
+ ? setTimeout(() => this.startUpdate(), DebounceTime)
191
+ : -1;
192
+
193
+ if (this.composing !== CompositionState.None) {
194
+ for (const tr of update.transactions) {
195
+ if (getUserEvent(tr) === 'input') {
196
+ this.composing = CompositionState.Changed;
197
+ } else if (this.composing === CompositionState.Changed && tr.selection) {
198
+ this.composing = CompositionState.ChangedAndMoved;
199
+ }
200
+ }
201
+ }
202
+ }
203
+
204
+ startUpdate() {
205
+ this.debounceUpdate = -1;
206
+ const { state } = this.view,
207
+ cState = state.field(completionState);
208
+ for (const active of cState.active) {
209
+ if (
210
+ active.state === State.Pending &&
211
+ !this.running.some((r) => r.active.source === active.source)
212
+ ) {
213
+ this.startQuery(active);
214
+ }
215
+ }
216
+ }
217
+
218
+ startQuery(active: ActiveSource) {
219
+ const { state } = this.view,
220
+ pos = cur(state);
221
+ const context = new CompletionContext(state, pos, active.explicitPos === pos);
222
+ const pending = new RunningQuery(active, context);
223
+ this.running.push(pending);
224
+ Promise.resolve(active.source(context))
225
+ .then(
226
+ (result) => {
227
+ if (!pending.context.aborted) {
228
+ pending.done = result || null;
229
+ return this.scheduleAccept();
230
+ }
231
+ return undefined;
232
+ },
233
+ (err) => {
234
+ this.view.dispatch({ effects: closeCompletionEffect.of(null) });
235
+ logException(this.view.state, err);
236
+ return undefined;
237
+ },
238
+ )
239
+ .catch(() => {
240
+ //
241
+ });
242
+ }
243
+
244
+ scheduleAccept() {
245
+ if (this.running.every((q) => q.done !== undefined)) {
246
+ this.accept();
247
+ } else if (this.debounceAccept < 0) {
248
+ this.debounceAccept = setTimeout(() => this.accept(), DebounceTime);
249
+ }
250
+ }
251
+
252
+ // For each finished query in this.running, try to create a result
253
+ // or, if appropriate, restart the query.
254
+ accept() {
255
+ if (this.debounceAccept > -1) {
256
+ clearTimeout(this.debounceAccept);
257
+ }
258
+ this.debounceAccept = -1;
259
+
260
+ const updated: ActiveSource[] = [];
261
+ const conf = this.view.state.facet(completionConfig);
262
+ for (let i = 0; i < this.running.length; i++) {
263
+ const query = this.running[i];
264
+ if (query.done === undefined) {
265
+ continue;
266
+ }
267
+ this.running.splice(i--, 1);
268
+
269
+ if (query.done) {
270
+ let active: ActiveSource = new ActiveResult(
271
+ query.active.source,
272
+ query.active.explicitPos,
273
+ query.done,
274
+ query.done.from,
275
+ query.done.to ??
276
+ cur(query.updates.length ? query.updates[0].startState : this.view.state),
277
+ );
278
+ // Replay the transactions that happened since the start of
279
+ // the request and see if that preserves the result
280
+ for (const tr of query.updates) {
281
+ active = active.update(tr, conf);
282
+ }
283
+ if (active.hasResult()) {
284
+ updated.push(active);
285
+ continue;
286
+ }
287
+ }
288
+
289
+ const current = this.view.state
290
+ .field(completionState)
291
+ .active.find((a) => a.source === query.active.source);
292
+ if (current && current.state === State.Pending) {
293
+ if (query.done === null) {
294
+ // Explicitly failed. Should clear the pending status if it
295
+ // hasn't been re-set in the meantime.
296
+ let active = new ActiveSource(query.active.source, State.Inactive);
297
+ for (const tr of query.updates) {
298
+ active = active.update(tr, conf);
299
+ }
300
+ if (active.state !== State.Pending) {
301
+ updated.push(active);
302
+ }
303
+ } else {
304
+ // Cleared by subsequent transactions. Restart.
305
+ this.startQuery(current);
306
+ }
307
+ }
308
+ }
309
+
310
+ if (updated.length) {
311
+ this.view.dispatch({ effects: setActiveEffect.of(updated) });
312
+ }
313
+ }
314
+ },
315
+ {
316
+ eventHandlers: {
317
+ blur() {
318
+ const state = this.view.state.field(completionState, false);
319
+ if (
320
+ state &&
321
+ state.tooltip &&
322
+ this.view.state.facet(completionConfig).closeOnBlur
323
+ ) {
324
+ this.view.dispatch({ effects: closeCompletionEffect.of(null) });
325
+ }
326
+ },
327
+ compositionstart() {
328
+ this.composing = CompositionState.Started;
329
+ },
330
+ compositionend() {
331
+ if (this.composing === CompositionState.ChangedAndMoved) {
332
+ // Safari fires compositionend events synchronously, possibly
333
+ // from inside an update, so dispatch asynchronously to avoid reentrancy
334
+ setTimeout(
335
+ () => this.view.dispatch({ effects: startCompletionEffect.of(false) }),
336
+ 20,
337
+ );
338
+ }
339
+ this.composing = CompositionState.None;
340
+ },
341
+ },
342
+ },
343
+ );
@@ -0,0 +1,118 @@
1
+ /* eslint-disable prefer-const */
2
+ /* eslint-disable @typescript-eslint/no-shadow */
3
+ import type { Text } from '@codemirror/state';
4
+
5
+ import type { Completion, CompletionSource } from './completion.js';
6
+
7
+ const enum C {
8
+ Range = 50000,
9
+ MinCacheLen = 1000,
10
+ MaxList = 2000,
11
+ }
12
+
13
+ function wordRE(wordChars: string) {
14
+ const escaped = wordChars.replace(/[\\[.+*?(){|^$]/g, '\\$&');
15
+ try {
16
+ return new RegExp(`[\\p{Alphabetic}\\p{Number}_${escaped}]+`, 'ug');
17
+ } catch {
18
+ // eslint-disable-next-line no-useless-escape
19
+ return new RegExp(`[\w${escaped}]`, 'g');
20
+ }
21
+ }
22
+
23
+ function mapRE(re: RegExp, f: (source: string) => string) {
24
+ return new RegExp(f(re.source), re.unicode ? 'u' : '');
25
+ }
26
+
27
+ const wordCaches: Record<string, WeakMap<Text, readonly Completion[]>> = Object.create(
28
+ null,
29
+ );
30
+
31
+ function wordCache(wordChars: string) {
32
+ return wordCaches[wordChars] || (wordCaches[wordChars] = new WeakMap());
33
+ }
34
+
35
+ function storeWords(
36
+ doc: Text,
37
+ wordRE: RegExp,
38
+ result: Completion[],
39
+ seen: Record<string, boolean>,
40
+ ignoreAt: number,
41
+ ) {
42
+ for (let lines = doc.iterLines(), pos = 0; !lines.next().done; ) {
43
+ let { value } = lines,
44
+ m;
45
+ wordRE.lastIndex = 0;
46
+ while ((m = wordRE.exec(value))) {
47
+ if (!seen[m[0]] && pos + m.index !== ignoreAt) {
48
+ result.push({ type: 'text', label: m[0] });
49
+ seen[m[0]] = true;
50
+ if (result.length >= C.MaxList) {
51
+ return;
52
+ }
53
+ }
54
+ }
55
+ pos += value.length + 1;
56
+ }
57
+ }
58
+
59
+ function collectWords(
60
+ doc: Text,
61
+ cache: WeakMap<Text, readonly Completion[]>,
62
+ wordRE: RegExp,
63
+ to: number,
64
+ ignoreAt: number,
65
+ ) {
66
+ const big = doc.length >= C.MinCacheLen;
67
+ const cached = big && cache.get(doc);
68
+ if (cached) {
69
+ return cached;
70
+ }
71
+ const result: Completion[] = [],
72
+ seen: Record<string, boolean> = Object.create(null);
73
+ if (doc.children) {
74
+ let pos = 0;
75
+ for (const ch of doc.children) {
76
+ if (ch.length >= C.MinCacheLen) {
77
+ for (const c of collectWords(ch, cache, wordRE, to - pos, ignoreAt - pos)) {
78
+ if (!seen[c.label]) {
79
+ seen[c.label] = true;
80
+ result.push(c);
81
+ }
82
+ }
83
+ } else {
84
+ storeWords(ch, wordRE, result, seen, ignoreAt - pos);
85
+ }
86
+ pos += ch.length + 1;
87
+ }
88
+ } else {
89
+ storeWords(doc, wordRE, result, seen, ignoreAt);
90
+ }
91
+ if (big && result.length < C.MaxList) {
92
+ cache.set(doc, result);
93
+ }
94
+ return result;
95
+ }
96
+
97
+ /// A completion source that will scan the document for words (using a
98
+ /// [character categorizer](#state.EditorState.charCategorizer)), and
99
+ /// return those as completions.
100
+ export const completeAnyWord: CompletionSource = (context) => {
101
+ const wordChars = context.state
102
+ .languageDataAt<string>('wordChars', context.pos)
103
+ .join('');
104
+ const re = wordRE(wordChars);
105
+ const token = context.matchBefore(mapRE(re, (s) => s + '$'));
106
+ if (!token && !context.explicit) {
107
+ return null;
108
+ }
109
+ const from = token ? token.from : context.pos;
110
+ const options = collectWords(
111
+ context.state.doc,
112
+ wordCache(wordChars),
113
+ re,
114
+ C.Range,
115
+ from,
116
+ );
117
+ return { from, options, validFor: mapRE(re, (s) => '^' + s) };
118
+ };
@@ -0,0 +1,61 @@
1
+ import type { CompletionSource, Completion } from '@codemirror/autocomplete';
2
+ import type { CompletionProvider, CompletionReply } from '@difizen/libro-code-editor';
3
+ import type { JSONObject } from '@difizen/libro-common';
4
+
5
+ type EditorCompletion = (provider: CompletionProvider | undefined) => CompletionSource;
6
+
7
+ export const kernelCompletions: EditorCompletion =
8
+ (provider: CompletionProvider | undefined) => async (context) => {
9
+ /**
10
+ * 只在显式的使用tab触发时调用kernel completion
11
+ * 只在只在隐式的输入时触发时调用lsp completion
12
+ */
13
+ if (!provider || !context.explicit) {
14
+ return null;
15
+ }
16
+ const word = context.matchBefore(/\w*/);
17
+ let result: CompletionReply;
18
+ const timeout = 500;
19
+ try {
20
+ result = await Promise.any([
21
+ provider({ cursorPosition: context.pos }),
22
+ new Promise<CompletionReply>((_resolve, reject) => {
23
+ setTimeout(() => {
24
+ reject(`request time out in ${timeout}ms`);
25
+ }, timeout);
26
+ }),
27
+ ]);
28
+ } catch (error) {
29
+ console.error('provider error', error);
30
+ return null;
31
+ }
32
+
33
+ if (!word) {
34
+ return null;
35
+ }
36
+ if (word.from === word.to) {
37
+ return null;
38
+ }
39
+
40
+ const metadata = result.metadata || {};
41
+ const types = metadata['_jupyter_types_experimental'] as JSONObject[];
42
+ let items: Completion[];
43
+ if (types) {
44
+ items = types.map((item) => {
45
+ return {
46
+ label: item['text'] as string,
47
+ type: item['type'] === '<unknown>' ? undefined : (item['type'] as string),
48
+ } as Completion;
49
+ });
50
+ } else {
51
+ items = result.matches.map((item) => {
52
+ return { label: item, type: 'text' };
53
+ });
54
+ }
55
+
56
+ return {
57
+ from: result.cursor_start ?? word.from,
58
+ to: result.cursor_end,
59
+ options: items,
60
+ };
61
+ };