@kerebron/extension-codemirror 0.4.28 → 0.4.30

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 (56) hide show
  1. package/esm/ExtensionCodeMirror.js +1 -0
  2. package/esm/ExtensionCodeMirror.js.map +1 -0
  3. package/esm/NodeCodeMirror.js +1 -0
  4. package/esm/NodeCodeMirror.js.map +1 -0
  5. package/esm/codeMirrorBlockNodeView.js +1 -0
  6. package/esm/codeMirrorBlockNodeView.js.map +1 -0
  7. package/esm/defaults.js +1 -0
  8. package/esm/defaults.js.map +1 -0
  9. package/esm/languageLoaders.js +1 -0
  10. package/esm/languageLoaders.js.map +1 -0
  11. package/esm/languages.js +1 -0
  12. package/esm/languages.js.map +1 -0
  13. package/esm/lsp/LSPExtension.js +1 -0
  14. package/esm/lsp/LSPExtension.js.map +1 -0
  15. package/esm/lsp/completion.js +1 -0
  16. package/esm/lsp/completion.js.map +1 -0
  17. package/esm/lsp/hover.js +1 -0
  18. package/esm/lsp/hover.js.map +1 -0
  19. package/esm/lsp/index.js +1 -0
  20. package/esm/lsp/index.js.map +1 -0
  21. package/esm/lsp/plugin.js +1 -0
  22. package/esm/lsp/plugin.js.map +1 -0
  23. package/esm/lsp/pos.js +1 -0
  24. package/esm/lsp/pos.js.map +1 -0
  25. package/esm/lsp/text.js +1 -0
  26. package/esm/lsp/text.js.map +1 -0
  27. package/esm/lsp/theme.js +1 -0
  28. package/esm/lsp/theme.js.map +1 -0
  29. package/esm/remote-selections.js +1 -0
  30. package/esm/remote-selections.js.map +1 -0
  31. package/esm/remote-sync.js +1 -0
  32. package/esm/remote-sync.js.map +1 -0
  33. package/esm/types.js +1 -0
  34. package/esm/types.js.map +1 -0
  35. package/esm/utils.js +1 -0
  36. package/esm/utils.js.map +1 -0
  37. package/package.json +9 -5
  38. package/src/ExtensionCodeMirror.ts +84 -0
  39. package/src/NodeCodeMirror.ts +135 -0
  40. package/src/codeMirrorBlockNodeView.ts +400 -0
  41. package/src/defaults.ts +80 -0
  42. package/src/languageLoaders.ts +401 -0
  43. package/src/languages.ts +109 -0
  44. package/src/lsp/LSPExtension.ts +42 -0
  45. package/src/lsp/completion.ts +231 -0
  46. package/src/lsp/hover.ts +100 -0
  47. package/src/lsp/index.ts +55 -0
  48. package/src/lsp/plugin.ts +128 -0
  49. package/src/lsp/pos.ts +12 -0
  50. package/src/lsp/text.ts +63 -0
  51. package/src/lsp/theme.ts +81 -0
  52. package/src/remote-selections.ts +263 -0
  53. package/src/remote-sync.ts +23 -0
  54. package/src/types.ts +55 -0
  55. package/src/utils.ts +336 -0
  56. package/assets/codemirror.css +0 -80
@@ -0,0 +1,400 @@
1
+ import { Node } from 'prosemirror-model';
2
+ import {
3
+ Decoration,
4
+ DecorationSource,
5
+ EditorView,
6
+ EditorView as PMEditorView,
7
+ NodeView,
8
+ } from 'prosemirror-view';
9
+
10
+ import {
11
+ drawSelection,
12
+ EditorView as CodeMirror,
13
+ highlightActiveLine,
14
+ highlightActiveLineGutter,
15
+ keymap,
16
+ lineNumbers,
17
+ rectangularSelection,
18
+ } from '@codemirror/view';
19
+ import { selectNextOccurrence } from '@codemirror/search';
20
+ import {
21
+ bracketMatching,
22
+ defaultHighlightStyle,
23
+ foldGutter,
24
+ foldKeymap,
25
+ indentOnInput,
26
+ syntaxHighlighting,
27
+ } from '@codemirror/language';
28
+ import {
29
+ autocompletion,
30
+ closeBrackets,
31
+ closeBracketsKeymap,
32
+ CompletionContext,
33
+ completionKeymap,
34
+ } from '@codemirror/autocomplete';
35
+ import { defaultKeymap, indentWithTab } from '@codemirror/commands';
36
+ import { Compartment, EditorState } from '@codemirror/state';
37
+
38
+ import { CoreEditor } from '@kerebron/editor';
39
+ import type { ExtensionLsp } from '@kerebron/extension-lsp';
40
+
41
+ import {
42
+ backspaceHandler,
43
+ computeChange,
44
+ forwardSelection,
45
+ maybeEscape,
46
+ setMode,
47
+ setTheme,
48
+ valueChanged,
49
+ } from './utils.js';
50
+ import { CodeBlockSettings } from './types.js';
51
+ import { RemoteSyncConfig, remoteSyncFacet } from './remote-sync.js';
52
+ import {
53
+ yRemoteSelections,
54
+ yRemoteSelectionsTheme,
55
+ } from './remote-selections.js';
56
+ import { languageServerExtensions, LSPPlugin } from './lsp/index.js';
57
+ import { LSPExtension } from './lsp/LSPExtension.js';
58
+
59
+ export const themeCallbacks: Array<(theme: string) => void> = [];
60
+
61
+ class CodeMirrorBlockNodeView implements NodeView {
62
+ dom: HTMLDivElement;
63
+ codeMirrorView: CodeMirror;
64
+ updating: boolean;
65
+ createCopyButtonCB: () => void;
66
+ selectDeleteCB: undefined | (() => void);
67
+ languageConf: Compartment;
68
+
69
+ uri: string = 'file:///' + Math.random() + '.ts';
70
+ diagListener?: (event: Event) => void;
71
+
72
+ constructor(
73
+ private node: Node,
74
+ private view: EditorView,
75
+ private getPos: () => number | undefined,
76
+ private settings: CodeBlockSettings,
77
+ private editor: CoreEditor,
78
+ ) {
79
+ this.updating = false;
80
+ const dom = document.createElement('div');
81
+ this.dom = dom;
82
+ dom.className = 'codeblock-root';
83
+ this.languageConf = new Compartment();
84
+ const themeConfig = new Compartment();
85
+
86
+ const yCollab = () => {
87
+ const plugins = [];
88
+ const remoteSyncConfig = new RemoteSyncConfig(
89
+ () => this.node,
90
+ getPos,
91
+ editor,
92
+ );
93
+ plugins.push(remoteSyncFacet.of(remoteSyncConfig));
94
+ plugins.push(
95
+ yRemoteSelectionsTheme,
96
+ yRemoteSelections,
97
+ );
98
+ return plugins;
99
+ };
100
+
101
+ const extensions = [
102
+ EditorState.readOnly.of(!!settings.readOnly),
103
+ CodeMirror.editable.of(!settings.readOnly),
104
+ lineNumbers(),
105
+ highlightActiveLineGutter(),
106
+ foldGutter(),
107
+ bracketMatching(),
108
+ closeBrackets(),
109
+ rectangularSelection(),
110
+ drawSelection({ cursorBlinkRate: 1000 }), // broken focus
111
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
112
+ this.languageConf.of([]),
113
+ indentOnInput(),
114
+ keymap.of([
115
+ { key: 'Mod-d', run: selectNextOccurrence, preventDefault: true },
116
+ {
117
+ key: 'ArrowUp',
118
+ run: (cmView) => maybeEscape('line', -1, cmView, view, getPos),
119
+ },
120
+ {
121
+ key: 'ArrowLeft',
122
+ run: (cmView) => maybeEscape('char', -1, cmView, view, getPos),
123
+ },
124
+ {
125
+ key: 'ArrowDown',
126
+ run: (cmView) => maybeEscape('line', 1, cmView, view, getPos),
127
+ },
128
+ {
129
+ key: 'ArrowRight',
130
+ run: (cmView) => maybeEscape('char', 1, cmView, view, getPos),
131
+ },
132
+ {
133
+ key: 'Ctrl-Enter',
134
+ run: () => {
135
+ if (!editor.run.exitCode()) {
136
+ return false;
137
+ }
138
+ view.focus();
139
+ return true;
140
+ },
141
+ },
142
+ {
143
+ key: 'Mod-z',
144
+ run: () => settings.undo?.(view.state, view.dispatch) || true,
145
+ shift: () => settings.redo?.(view.state, view.dispatch) || true,
146
+ },
147
+ {
148
+ key: 'Mod-y',
149
+ run: () => settings.redo?.(view.state, view.dispatch) || true,
150
+ },
151
+ {
152
+ key: 'Backspace',
153
+ run: (cmView) => backspaceHandler(view, cmView, editor),
154
+ },
155
+ {
156
+ key: 'Mod-Backspace',
157
+ run: (cmView) => backspaceHandler(view, cmView, editor),
158
+ },
159
+ {
160
+ key: 'Mod-a',
161
+ run: () => {
162
+ const result = editor.run.selectAll();
163
+ view.focus();
164
+ return result;
165
+ },
166
+ },
167
+ ...defaultKeymap,
168
+ ...foldKeymap,
169
+ ...closeBracketsKeymap,
170
+ ...completionKeymap,
171
+ indentWithTab,
172
+ {
173
+ key: 'Ctrl-`',
174
+ run: () => {
175
+ editor.chain().toggleDevToolkit().run();
176
+ return true;
177
+ },
178
+ },
179
+ ]),
180
+ ...(settings.theme ? settings.theme : []),
181
+ themeConfig.of([]),
182
+ yCollab(),
183
+ ];
184
+
185
+ const extensionLsp: ExtensionLsp | undefined = editor.getExtension('lsp');
186
+ if (extensionLsp) {
187
+ const extension = new LSPExtension({
188
+ getPos: this.getPos,
189
+ extensions: languageServerExtensions(),
190
+ });
191
+ extensions.push(extension.plugin(extensionLsp, editor));
192
+ } else {
193
+ // Define custom completions
194
+ const myCompletions = [
195
+ { label: 'console.log', type: 'function' },
196
+ { label: 'const', type: 'keyword' },
197
+ { label: 'let', type: 'keyword' },
198
+ { label: 'var', type: 'keyword' },
199
+ { label: 'function', type: 'keyword' },
200
+ ];
201
+
202
+ // Custom autocomplete function
203
+ const complete = (context: CompletionContext) => {
204
+ let word = context.matchBefore(/\w*/);
205
+ if (!word) {
206
+ return null;
207
+ }
208
+ if (word.from == word.to && !context.explicit) return null;
209
+ return {
210
+ from: word.from,
211
+ options: myCompletions,
212
+ };
213
+ };
214
+
215
+ extensions.push(
216
+ autocompletion({
217
+ override: [
218
+ complete,
219
+ ],
220
+ }),
221
+ );
222
+ }
223
+
224
+ const state = EditorState.create({
225
+ extensions,
226
+ doc: node.textContent,
227
+ });
228
+
229
+ const root = (editor.view && 'root' in editor.view)
230
+ ? editor.view.root
231
+ : document || document;
232
+
233
+ this.codeMirrorView = new CodeMirror({
234
+ state,
235
+ root,
236
+ dispatch: (tr) => {
237
+ this.codeMirrorView.update([tr]);
238
+ if (!this.updating) {
239
+ const textUpdate = tr.state.toJSON().doc;
240
+ valueChanged(textUpdate, this.node, getPos, view);
241
+ forwardSelection(this.codeMirrorView, view, getPos);
242
+
243
+ const lspPlugin = LSPPlugin.get(this.codeMirrorView);
244
+ if (lspPlugin) {
245
+ lspPlugin.update();
246
+ }
247
+ }
248
+ },
249
+ });
250
+ dom.append(this.codeMirrorView.dom);
251
+
252
+ if (
253
+ !(Array.isArray(settings.languageWhitelist) &&
254
+ settings.languageWhitelist.length === 1)
255
+ ) {
256
+ this.selectDeleteCB = settings.createSelect(
257
+ settings,
258
+ dom,
259
+ node,
260
+ view,
261
+ getPos,
262
+ );
263
+ }
264
+
265
+ this.createCopyButtonCB = settings.createCopyButton(
266
+ settings,
267
+ dom,
268
+ node,
269
+ view,
270
+ this.codeMirrorView,
271
+ getPos,
272
+ );
273
+
274
+ setMode(node.attrs.lang, this.codeMirrorView, settings, this.languageConf);
275
+
276
+ const currentTheme = settings.getCurrentTheme?.();
277
+ setTheme(this.codeMirrorView, themeConfig, [
278
+ settings.themes.find((t) => t.name === currentTheme),
279
+ ]);
280
+
281
+ this.updateTheme = (theme: string) => {
282
+ setTheme(this.codeMirrorView, themeConfig, [
283
+ settings.themes.find((t) => t.name === theme),
284
+ ]);
285
+ };
286
+ themeCallbacks.push(this.updateTheme);
287
+ }
288
+
289
+ updateTheme(updateTheme: any) {
290
+ throw new Error('Method not implemented.');
291
+ }
292
+
293
+ selectNode() {
294
+ this.codeMirrorView.focus();
295
+ }
296
+
297
+ stopEvent(e: Event) {
298
+ return this.settings.stopEvent(
299
+ e,
300
+ this.node,
301
+ this.getPos,
302
+ this.view,
303
+ this.dom,
304
+ );
305
+ }
306
+
307
+ setSelection(anchor: number, head: number) {
308
+ this.codeMirrorView.focus();
309
+ this.updating = true;
310
+ this.codeMirrorView.dispatch({ selection: { anchor, head } });
311
+ this.updating = false;
312
+ }
313
+
314
+ update(
315
+ updateNode: Node,
316
+ decorations: readonly Decoration[],
317
+ innerDecorations: DecorationSource,
318
+ ) {
319
+ const codeDecorations: Decoration[] = [];
320
+
321
+ innerDecorations
322
+ .forEachSet((set) =>
323
+ set.find()
324
+ .map((d) => {
325
+ codeDecorations.push(d);
326
+ })
327
+ );
328
+
329
+ // https://codemirror.net/examples/decoration/
330
+ if (updateNode.type.name !== this.node.type.name) return false;
331
+ if (updateNode.attrs.lang !== this.node.attrs.lang) {
332
+ setMode(
333
+ updateNode.attrs.lang,
334
+ this.codeMirrorView,
335
+ this.settings,
336
+ this.languageConf,
337
+ );
338
+ }
339
+
340
+ const oldNode = this.node;
341
+ this.node = updateNode;
342
+ const change = computeChange(
343
+ this.codeMirrorView.state.doc.toString(),
344
+ this.node.textContent,
345
+ );
346
+ if (change) {
347
+ this.updating = true;
348
+ this.codeMirrorView.dispatch({
349
+ changes: {
350
+ from: change.from,
351
+ to: change.to,
352
+ insert: change.text,
353
+ },
354
+ selection: { anchor: change.from + change.text.length },
355
+ // effects <- codeDecorations
356
+ });
357
+ this.updating = false;
358
+ }
359
+ this.settings.updateSelect(
360
+ this.settings,
361
+ this.dom,
362
+ updateNode,
363
+ this.view,
364
+ this.getPos,
365
+ oldNode,
366
+ );
367
+ return true;
368
+ }
369
+
370
+ ignoreMutation() {
371
+ return true;
372
+ }
373
+
374
+ destroy() {
375
+ if (this.selectDeleteCB) {
376
+ this.selectDeleteCB();
377
+ }
378
+ this.createCopyButtonCB();
379
+ themeCallbacks.splice(themeCallbacks.indexOf(this.updateTheme), 1);
380
+ }
381
+ }
382
+
383
+ export const codeMirrorBlockNodeView: (
384
+ settings: CodeBlockSettings,
385
+ editor: CoreEditor,
386
+ ) => (
387
+ node: Node,
388
+ view: EditorView,
389
+ getPos: () => number | undefined,
390
+ decorations: readonly Decoration[],
391
+ innerDecorations: DecorationSource,
392
+ ) => NodeView = (settings, editor) => {
393
+ return (
394
+ pmNode: Node,
395
+ view: PMEditorView,
396
+ getPos: () => number | undefined,
397
+ ) => {
398
+ return new CodeMirrorBlockNodeView(pmNode, view, getPos, settings, editor);
399
+ };
400
+ };
@@ -0,0 +1,80 @@
1
+ import { Node } from 'prosemirror-model';
2
+ import { EditorView } from 'prosemirror-view';
3
+
4
+ import { CodeBlockSettings } from './types.js';
5
+
6
+ export const defaultCreateSelect = (
7
+ settings: CodeBlockSettings,
8
+ dom: HTMLElement,
9
+ node: Node,
10
+ view: EditorView,
11
+ getPos: () => number | undefined,
12
+ ) => {
13
+ if (!settings.languageLoaders) return () => {};
14
+ const { languageLoaders } = settings;
15
+ const select = document.createElement('select');
16
+ select.className = 'codeblock-select';
17
+ const noneOption = document.createElement('option');
18
+ noneOption.value = 'none';
19
+ noneOption.textContent = settings.languageNameMap?.none || 'none';
20
+ select.append(noneOption);
21
+ Object.keys(languageLoaders)
22
+ .sort()
23
+ .forEach((lang) => {
24
+ if (
25
+ settings.languageWhitelist &&
26
+ !settings.languageWhitelist.includes(lang)
27
+ ) {
28
+ return;
29
+ }
30
+ const option = document.createElement('option');
31
+ option.value = lang;
32
+ option.textContent = settings.languageNameMap?.[lang] || lang;
33
+ select.append(option);
34
+ });
35
+ select.value = node.attrs.lang || 'none';
36
+ dom.prepend(select);
37
+ select.onchange = (e) => {
38
+ if (!(e.target instanceof HTMLSelectElement)) return;
39
+ const lang = e.target.value;
40
+ const pos = getPos();
41
+ if (pos) {
42
+ view.dispatch(
43
+ view.state.tr.setNodeMarkup(pos, undefined, {
44
+ ...node.attrs,
45
+ lang,
46
+ }),
47
+ );
48
+ }
49
+ };
50
+ // Delete code.
51
+ return () => {};
52
+ };
53
+
54
+ const defaultUpdateSelect = (
55
+ settings: CodeBlockSettings,
56
+ dom: HTMLElement,
57
+ node: Node,
58
+ view: EditorView,
59
+ getPos: () => number | undefined,
60
+ oldNode: Node,
61
+ ) => {
62
+ if (oldNode.attrs.lang !== node.attrs.lang) {
63
+ const select = dom.querySelector('.codeblock-select');
64
+ if (!(select instanceof HTMLSelectElement)) return;
65
+ select.value = node.attrs.lang || 'none';
66
+ }
67
+ };
68
+
69
+ const defaultStopEvent = () => true;
70
+
71
+ export const defaultSettings: CodeBlockSettings = {
72
+ createSelect: defaultCreateSelect,
73
+ updateSelect: defaultUpdateSelect,
74
+ createCopyButton: () => {
75
+ return () => {};
76
+ },
77
+ stopEvent: defaultStopEvent,
78
+ readOnly: false,
79
+ themes: [],
80
+ };