@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,112 @@
1
+ /* eslint-disable @typescript-eslint/no-use-before-define */
2
+ import type { Extension, EditorState, StateEffect } from '@codemirror/state';
3
+ import { Prec } from '@codemirror/state';
4
+ import type { KeyBinding } from '@codemirror/view';
5
+ import { keymap } from '@codemirror/view';
6
+
7
+ import { indentOrCompletion, indentOrTooltip } from '../indent.js';
8
+
9
+ import type { Completion, Option } from './completion.js';
10
+ import type { CompletionConfig } from './config.js';
11
+ import { completionConfig } from './config.js';
12
+ import { completionState, State, setSelectedEffect } from './state.js';
13
+ import { baseTheme } from './theme.js';
14
+ import {
15
+ completionPlugin,
16
+ moveCompletionSelection,
17
+ acceptCompletion,
18
+ closeCompletion,
19
+ } from './view.js';
20
+
21
+ export * from './snippet.js';
22
+ export * from './completion.js';
23
+ export * from './view.js';
24
+ export * from './word.js';
25
+ export * from './closebrackets.js';
26
+
27
+ /// Returns an extension that enables autocompletion.
28
+ export function autocompletion(config: CompletionConfig = {}): Extension {
29
+ return [
30
+ completionState,
31
+ completionConfig.of(config),
32
+ completionPlugin,
33
+ completionKeymapExt,
34
+ baseTheme,
35
+ ];
36
+ }
37
+
38
+ /// Basic keybindings for autocompletion.
39
+ ///
40
+ /// - Ctrl-Space: [`startCompletion`](#autocomplete.startCompletion)
41
+ /// - Escape: [`closeCompletion`](#autocomplete.closeCompletion)
42
+ /// - ArrowDown: [`moveCompletionSelection`](#autocomplete.moveCompletionSelection)`(true)`
43
+ /// - ArrowUp: [`moveCompletionSelection`](#autocomplete.moveCompletionSelection)`(false)`
44
+ /// - PageDown: [`moveCompletionSelection`](#autocomplete.moveCompletionSelection)`(true, "page")`
45
+ /// - PageDown: [`moveCompletionSelection`](#autocomplete.moveCompletionSelection)`(true, "page")`
46
+ /// - Enter: [`acceptCompletion`](#autocomplete.acceptCompletion)
47
+ export const completionKeymap: readonly KeyBinding[] = [
48
+ { key: 'Tab', run: indentOrCompletion, shift: indentOrTooltip },
49
+ { key: 'Escape', run: closeCompletion },
50
+ { key: 'ArrowDown', run: moveCompletionSelection(true) },
51
+ { key: 'ArrowUp', run: moveCompletionSelection(false) },
52
+ { key: 'PageDown', run: moveCompletionSelection(true, 'page') },
53
+ { key: 'PageUp', run: moveCompletionSelection(false, 'page') },
54
+ { key: 'Enter', run: acceptCompletion },
55
+ ];
56
+
57
+ const completionKeymapExt = Prec.highest(
58
+ keymap.computeN([completionConfig], (state) =>
59
+ state.facet(completionConfig).defaultKeymap ? [completionKeymap] : [],
60
+ ),
61
+ );
62
+
63
+ /// Get the current completion status. When completions are available,
64
+ /// this will return `"active"`. When completions are pending (in the
65
+ /// process of being queried), this returns `"pending"`. Otherwise, it
66
+ /// returns `null`.
67
+ export function completionStatus(state: EditorState): null | 'active' | 'pending' {
68
+ const cState = state.field(completionState, false);
69
+ return cState && cState.active.some((a) => a.state === State.Pending)
70
+ ? 'pending'
71
+ : cState && cState.active.some((a) => a.state !== State.Inactive)
72
+ ? 'active'
73
+ : null;
74
+ }
75
+
76
+ const completionArrayCache: WeakMap<readonly Option[], readonly Completion[]> =
77
+ new WeakMap();
78
+
79
+ /// Returns the available completions as an array.
80
+ export function currentCompletions(state: EditorState): readonly Completion[] {
81
+ const open = state.field(completionState, false)?.open;
82
+ if (!open) {
83
+ return [];
84
+ }
85
+ let completions = completionArrayCache.get(open.options);
86
+ if (!completions) {
87
+ completionArrayCache.set(
88
+ open.options,
89
+ (completions = open.options.map((o) => o.completion)),
90
+ );
91
+ }
92
+ return completions;
93
+ }
94
+
95
+ /// Return the currently selected completion, if any.
96
+ export function selectedCompletion(state: EditorState): Completion | null {
97
+ const open = state.field(completionState, false)?.open;
98
+ return open && open.selected >= 0 ? open.options[open.selected].completion : null;
99
+ }
100
+
101
+ /// Returns the currently selected position in the active completion
102
+ /// list, or null if no completions are active.
103
+ export function selectedCompletionIndex(state: EditorState): number | null {
104
+ const open = state.field(completionState, false)?.open;
105
+ return open && open.selected >= 0 ? open.selected : null;
106
+ }
107
+
108
+ /// Create an effect that can be attached to a transaction to change
109
+ /// the currently selected completion.
110
+ export function setSelectedCompletion(index: number): StateEffect<unknown> {
111
+ return setSelectedEffect.of(index);
112
+ }
@@ -0,0 +1,394 @@
1
+ /* eslint-disable @typescript-eslint/no-use-before-define */
2
+ /* eslint-disable @typescript-eslint/no-shadow */
3
+ /* eslint-disable prefer-const */
4
+ /* eslint-disable no-param-reassign */
5
+ /* eslint-disable @typescript-eslint/no-parameter-properties */
6
+ /* eslint-disable @typescript-eslint/parameter-properties */
7
+ import { indentUnit } from '@codemirror/language';
8
+ import type {
9
+ ChangeDesc,
10
+ EditorState,
11
+ Transaction,
12
+ TransactionSpec,
13
+ StateCommand,
14
+ } from '@codemirror/state';
15
+ import {
16
+ StateField,
17
+ StateEffect,
18
+ EditorSelection,
19
+ Text,
20
+ Prec,
21
+ Facet,
22
+ MapMode,
23
+ } from '@codemirror/state';
24
+ import type { DecorationSet, KeyBinding } from '@codemirror/view';
25
+ import { Decoration, WidgetType, EditorView, keymap } from '@codemirror/view';
26
+
27
+ import type { Completion } from './completion.js';
28
+ import { baseTheme } from './theme.js';
29
+
30
+ class FieldPos {
31
+ constructor(
32
+ public field: number,
33
+ readonly line: number,
34
+ public from: number,
35
+ public to: number,
36
+ ) {}
37
+ }
38
+
39
+ class FieldRange {
40
+ constructor(
41
+ readonly field: number,
42
+ readonly from: number,
43
+ readonly to: number,
44
+ ) {}
45
+
46
+ map(changes: ChangeDesc) {
47
+ const from = changes.mapPos(this.from, -1, MapMode.TrackDel);
48
+ const to = changes.mapPos(this.to, 1, MapMode.TrackDel);
49
+ return from === null || to === null ? null : new FieldRange(this.field, from, to);
50
+ }
51
+ }
52
+
53
+ class Snippet {
54
+ constructor(
55
+ readonly lines: readonly string[],
56
+ readonly fieldPositions: readonly FieldPos[],
57
+ ) {}
58
+
59
+ instantiate(state: EditorState, pos: number) {
60
+ const text = [],
61
+ lineStart = [pos];
62
+ const lineObj = state.doc.lineAt(pos),
63
+ baseIndent = /^\s*/.exec(lineObj.text)![0];
64
+ for (let line of this.lines) {
65
+ if (text.length) {
66
+ let indent = baseIndent,
67
+ tabs = /^\t*/.exec(line)![0].length;
68
+ for (let i = 0; i < tabs; i++) {
69
+ indent += state.facet(indentUnit);
70
+ }
71
+ lineStart.push(pos + indent.length - tabs);
72
+ line = indent + line.slice(tabs);
73
+ }
74
+ text.push(line);
75
+ pos += line.length + 1;
76
+ }
77
+ const ranges = this.fieldPositions.map(
78
+ (pos) =>
79
+ new FieldRange(
80
+ pos.field,
81
+ lineStart[pos.line] + pos.from,
82
+ lineStart[pos.line] + pos.to,
83
+ ),
84
+ );
85
+ return { text, ranges };
86
+ }
87
+
88
+ static parse(template: string) {
89
+ const fields: { seq: number | null; name: string }[] = [];
90
+ let lines = [],
91
+ positions = [],
92
+ m;
93
+ for (let line of template.split(/\r\n?|\n/)) {
94
+ while ((m = /[#$]\{(?:(\d+)(?::([^}]*))?|([^}]*))\}/.exec(line))) {
95
+ let seq = m[1] ? +m[1] : null,
96
+ name = m[2] || m[3] || '',
97
+ found = -1;
98
+ for (let i = 0; i < fields.length; i++) {
99
+ if (
100
+ seq !== null
101
+ ? fields[i].seq === seq
102
+ : name
103
+ ? fields[i].name === name
104
+ : false
105
+ ) {
106
+ found = i;
107
+ }
108
+ }
109
+ if (found < 0) {
110
+ let i = 0;
111
+ while (
112
+ i < fields.length &&
113
+ (seq === null || (fields[i].seq !== null && fields[i].seq! < seq))
114
+ ) {
115
+ i++;
116
+ }
117
+ fields.splice(i, 0, { seq, name });
118
+ found = i;
119
+ for (const pos of positions) {
120
+ if (pos.field >= found) {
121
+ pos.field++;
122
+ }
123
+ }
124
+ }
125
+ positions.push(
126
+ new FieldPos(found, lines.length, m.index, m.index + name.length),
127
+ );
128
+ line = line.slice(0, m.index) + name + line.slice(m.index + m[0].length);
129
+ }
130
+ for (let esc; (esc = /([$#])\\{/.exec(line)); ) {
131
+ line =
132
+ line.slice(0, esc.index) +
133
+ esc[1] +
134
+ '{' +
135
+ line.slice(esc.index + esc[0].length);
136
+ for (const pos of positions) {
137
+ if (pos.line === lines.length && pos.from > esc.index) {
138
+ pos.from--;
139
+ pos.to--;
140
+ }
141
+ }
142
+ }
143
+ lines.push(line);
144
+ }
145
+ return new Snippet(lines, positions);
146
+ }
147
+ }
148
+
149
+ const fieldMarker = Decoration.widget({
150
+ widget: new (class extends WidgetType {
151
+ toDOM() {
152
+ const span = document.createElement('span');
153
+ span.className = 'cm-snippetFieldPosition';
154
+ return span;
155
+ }
156
+ override ignoreEvent() {
157
+ return false;
158
+ }
159
+ })(),
160
+ });
161
+ const fieldRange = Decoration.mark({ class: 'cm-snippetField' });
162
+
163
+ class ActiveSnippet {
164
+ deco: DecorationSet;
165
+
166
+ constructor(
167
+ readonly ranges: readonly FieldRange[],
168
+ readonly active: number,
169
+ ) {
170
+ this.deco = Decoration.set(
171
+ ranges.map((r) =>
172
+ (r.from === r.to ? fieldMarker : fieldRange).range(r.from, r.to),
173
+ ),
174
+ );
175
+ }
176
+
177
+ map(changes: ChangeDesc) {
178
+ const ranges = [];
179
+ for (const r of this.ranges) {
180
+ const mapped = r.map(changes);
181
+ if (!mapped) {
182
+ return null;
183
+ }
184
+ ranges.push(mapped);
185
+ }
186
+ return new ActiveSnippet(ranges, this.active);
187
+ }
188
+
189
+ selectionInsideField(sel: EditorSelection) {
190
+ return sel.ranges.every((range) =>
191
+ this.ranges.some(
192
+ (r) => r.field === this.active && r.from <= range.from && r.to >= range.to,
193
+ ),
194
+ );
195
+ }
196
+ }
197
+
198
+ const setActive = StateEffect.define<ActiveSnippet | null>({
199
+ map(value, changes) {
200
+ return value && value.map(changes);
201
+ },
202
+ });
203
+
204
+ const moveToField = StateEffect.define<number>();
205
+
206
+ const snippetState = StateField.define<ActiveSnippet | null>({
207
+ create() {
208
+ return null;
209
+ },
210
+
211
+ update(value, tr) {
212
+ for (const effect of tr.effects) {
213
+ if (effect.is(setActive)) {
214
+ return effect.value;
215
+ }
216
+ if (effect.is(moveToField) && value) {
217
+ return new ActiveSnippet(value.ranges, effect.value);
218
+ }
219
+ }
220
+ if (value && tr.docChanged) {
221
+ value = value.map(tr.changes);
222
+ }
223
+ if (value && tr.selection && !value.selectionInsideField(tr.selection)) {
224
+ value = null;
225
+ }
226
+ return value;
227
+ },
228
+
229
+ provide: (f) =>
230
+ EditorView.decorations.from(f, (val) => (val ? val.deco : Decoration.none)),
231
+ });
232
+
233
+ function fieldSelection(ranges: readonly FieldRange[], field: number) {
234
+ return EditorSelection.create(
235
+ ranges
236
+ .filter((r) => r.field === field)
237
+ .map((r) => EditorSelection.range(r.from, r.to)),
238
+ );
239
+ }
240
+
241
+ /// Convert a snippet template to a function that can
242
+ /// [apply](#autocomplete.Completion.apply) it. Snippets are written
243
+ /// using syntax like this:
244
+ ///
245
+ /// "for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
246
+ ///
247
+ /// Each `${}` placeholder (you may also use `#{}`) indicates a field
248
+ /// that the user can fill in. Its name, if any, will be the default
249
+ /// content for the field.
250
+ ///
251
+ /// When the snippet is activated by calling the returned function,
252
+ /// the code is inserted at the given position. Newlines in the
253
+ /// template are indented by the indentation of the start line, plus
254
+ /// one [indent unit](#language.indentUnit) per tab character after
255
+ /// the newline.
256
+ ///
257
+ /// On activation, (all instances of) the first field are selected.
258
+ /// The user can move between fields with Tab and Shift-Tab as long as
259
+ /// the fields are active. Moving to the last field or moving the
260
+ /// cursor out of the current field deactivates the fields.
261
+ ///
262
+ /// The order of fields defaults to textual order, but you can add
263
+ /// numbers to placeholders (`${1}` or `${1:defaultText}`) to provide
264
+ /// a custom order.
265
+ ///
266
+ /// To include a literal `${` or `#{` in your template, put a
267
+ /// backslash after the dollar or hash and before the brace (`$\\{`).
268
+ /// This will be removed and the sequence will not be interpreted as a
269
+ /// placeholder.
270
+ export function snippet(template: string) {
271
+ const snippet = Snippet.parse(template);
272
+ return (
273
+ editor: { state: EditorState; dispatch: (tr: Transaction) => void },
274
+ _completion: Completion,
275
+ from: number,
276
+ to: number,
277
+ ) => {
278
+ const { text, ranges } = snippet.instantiate(editor.state, from);
279
+ const spec: TransactionSpec = {
280
+ changes: { from, to, insert: Text.of(text) },
281
+ scrollIntoView: true,
282
+ };
283
+ if (ranges.length) {
284
+ spec.selection = fieldSelection(ranges, 0);
285
+ }
286
+ if (ranges.length > 1) {
287
+ const active = new ActiveSnippet(ranges, 0);
288
+ const effects: StateEffect<unknown>[] = (spec.effects = [setActive.of(active)]);
289
+ if (editor.state.field(snippetState, false) === undefined) {
290
+ effects.push(
291
+ StateEffect.appendConfig.of([
292
+ snippetState,
293
+ addSnippetKeymap,
294
+ snippetPointerHandler,
295
+ baseTheme,
296
+ ]),
297
+ );
298
+ }
299
+ }
300
+ editor.dispatch(editor.state.update(spec));
301
+ };
302
+ }
303
+
304
+ function moveField(dir: 1 | -1): StateCommand {
305
+ return ({ state, dispatch }) => {
306
+ const active = state.field(snippetState, false);
307
+ if (!active || (dir < 0 && active.active === 0)) {
308
+ return false;
309
+ }
310
+ const next = active.active + dir,
311
+ last = dir > 0 && !active.ranges.some((r) => r.field === next + dir);
312
+ dispatch(
313
+ state.update({
314
+ selection: fieldSelection(active.ranges, next),
315
+ effects: setActive.of(last ? null : new ActiveSnippet(active.ranges, next)),
316
+ }),
317
+ );
318
+ return true;
319
+ };
320
+ }
321
+
322
+ /// A command that clears the active snippet, if any.
323
+ export const clearSnippet: StateCommand = ({ state, dispatch }) => {
324
+ const active = state.field(snippetState, false);
325
+ if (!active) {
326
+ return false;
327
+ }
328
+ dispatch(state.update({ effects: setActive.of(null) }));
329
+ return true;
330
+ };
331
+
332
+ /// Move to the next snippet field, if available.
333
+ export const nextSnippetField = moveField(1);
334
+
335
+ /// Move to the previous snippet field, if available.
336
+ export const prevSnippetField = moveField(-1);
337
+
338
+ const defaultSnippetKeymap = [
339
+ { key: 'Tab', run: nextSnippetField, shift: prevSnippetField },
340
+ { key: 'Escape', run: clearSnippet },
341
+ ];
342
+
343
+ /// A facet that can be used to configure the key bindings used by
344
+ /// snippets. The default binds Tab to
345
+ /// [`nextSnippetField`](#autocomplete.nextSnippetField), Shift-Tab to
346
+ /// [`prevSnippetField`](#autocomplete.prevSnippetField), and Escape
347
+ /// to [`clearSnippet`](#autocomplete.clearSnippet).
348
+ export const snippetKeymap = Facet.define<readonly KeyBinding[], readonly KeyBinding[]>(
349
+ {
350
+ combine(maps) {
351
+ return maps.length ? maps[0] : defaultSnippetKeymap;
352
+ },
353
+ },
354
+ );
355
+
356
+ const addSnippetKeymap = Prec.highest(
357
+ keymap.compute([snippetKeymap], (state) => state.facet(snippetKeymap)),
358
+ );
359
+
360
+ /// Create a completion from a snippet. Returns an object with the
361
+ /// properties from `completion`, plus an `apply` function that
362
+ /// applies the snippet.
363
+ export function snippetCompletion(
364
+ template: string,
365
+ completion: Completion,
366
+ ): Completion {
367
+ return { ...completion, apply: snippet(template) };
368
+ }
369
+
370
+ const snippetPointerHandler = EditorView.domEventHandlers({
371
+ mousedown(event, view) {
372
+ let active = view.state.field(snippetState, false),
373
+ pos: number | null;
374
+ if (
375
+ !active ||
376
+ (pos = view.posAtCoords({ x: event.clientX, y: event.clientY })) === null
377
+ ) {
378
+ return false;
379
+ }
380
+ const match = active.ranges.find((r) => r.from <= pos! && r.to >= pos!);
381
+ if (!match || match.field === active.active) {
382
+ return false;
383
+ }
384
+ view.dispatch({
385
+ selection: fieldSelection(active.ranges, match.field),
386
+ effects: setActive.of(
387
+ active.ranges.some((r) => r.field > match.field)
388
+ ? new ActiveSnippet(active.ranges, match.field)
389
+ : null,
390
+ ),
391
+ });
392
+ return true;
393
+ },
394
+ });