@difizen/libro-codemirror 0.0.0-snapshot-20241017072317

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