@jupyterlab/codemirror 4.0.0-alpha.2 → 4.0.0-alpha.20

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 (78) hide show
  1. package/lib/codemirror-ipythongfm.d.ts +9 -3
  2. package/lib/codemirror-ipythongfm.js +52 -30
  3. package/lib/codemirror-ipythongfm.js.map +1 -1
  4. package/lib/commands.d.ts +13 -0
  5. package/lib/commands.js +32 -0
  6. package/lib/commands.js.map +1 -0
  7. package/lib/editor.d.ts +48 -279
  8. package/lib/editor.js +220 -818
  9. package/lib/editor.js.map +1 -1
  10. package/lib/extension.d.ts +236 -0
  11. package/lib/extension.js +687 -0
  12. package/lib/extension.js.map +1 -0
  13. package/lib/extensions/customStyle.d.ts +25 -0
  14. package/lib/extensions/customStyle.js +51 -0
  15. package/lib/extensions/customStyle.js.map +1 -0
  16. package/lib/extensions/index.d.ts +3 -0
  17. package/lib/extensions/index.js +8 -0
  18. package/lib/extensions/index.js.map +1 -0
  19. package/lib/extensions/rulers.d.ts +8 -0
  20. package/lib/extensions/rulers.js +85 -0
  21. package/lib/extensions/rulers.js.map +1 -0
  22. package/lib/extensions/ybinding.d.ts +125 -0
  23. package/lib/extensions/ybinding.js +213 -0
  24. package/lib/extensions/ybinding.js.map +1 -0
  25. package/lib/factory.d.ts +15 -5
  26. package/lib/factory.js +53 -24
  27. package/lib/factory.js.map +1 -1
  28. package/lib/index.d.ts +7 -19
  29. package/lib/index.js +7 -23
  30. package/lib/index.js.map +1 -1
  31. package/lib/language.d.ts +96 -0
  32. package/lib/language.js +1690 -0
  33. package/lib/language.js.map +1 -0
  34. package/lib/mimetype.d.ts +3 -0
  35. package/lib/mimetype.js +16 -5
  36. package/lib/mimetype.js.map +1 -1
  37. package/lib/searchprovider.d.ts +197 -0
  38. package/lib/searchprovider.js +550 -0
  39. package/lib/searchprovider.js.map +1 -0
  40. package/lib/theme.d.ts +57 -0
  41. package/lib/theme.js +194 -0
  42. package/lib/theme.js.map +1 -0
  43. package/lib/token.d.ts +375 -0
  44. package/lib/token.js +18 -0
  45. package/lib/token.js.map +1 -0
  46. package/package.json +45 -41
  47. package/src/codemirror-ipythongfm.ts +109 -0
  48. package/src/commands.ts +34 -0
  49. package/src/editor.ts +783 -0
  50. package/src/extension.ts +878 -0
  51. package/src/extensions/customStyle.ts +79 -0
  52. package/src/extensions/index.ts +8 -0
  53. package/src/extensions/rulers.ts +112 -0
  54. package/src/extensions/ybinding.ts +267 -0
  55. package/src/factory.ts +100 -0
  56. package/src/index.ts +17 -0
  57. package/src/language.ts +1729 -0
  58. package/src/mimetype.ts +54 -0
  59. package/src/searchprovider.ts +700 -0
  60. package/src/theme.ts +219 -0
  61. package/src/token.ts +436 -0
  62. package/src/typings.d.ts +13 -0
  63. package/style/base.css +22 -162
  64. package/style/index.css +1 -7
  65. package/style/index.js +1 -7
  66. package/lib/codemirror-ipython.d.ts +0 -2
  67. package/lib/codemirror-ipython.js +0 -30
  68. package/lib/codemirror-ipython.js.map +0 -1
  69. package/lib/mode.d.ts +0 -81
  70. package/lib/mode.js +0 -149
  71. package/lib/mode.js.map +0 -1
  72. package/lib/syntaxstatus.d.ts +0 -67
  73. package/lib/syntaxstatus.js +0 -140
  74. package/lib/syntaxstatus.js.map +0 -1
  75. package/lib/tokens.d.ts +0 -18
  76. package/lib/tokens.js +0 -9
  77. package/lib/tokens.js.map +0 -1
  78. package/typings/codemirror/codemirror.d.ts +0 -227
@@ -0,0 +1,79 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import { combineConfig, Extension, Facet } from '@codemirror/state';
7
+ import { EditorView } from '@codemirror/view';
8
+
9
+ /**
10
+ * Editor customizable styles
11
+ */
12
+ export type CustomTheme = {
13
+ /**
14
+ * Font family
15
+ */
16
+ fontFamily: string | null;
17
+ /**
18
+ * Font size
19
+ */
20
+ fontSize: number | null;
21
+ /**
22
+ * Line height
23
+ */
24
+ lineHeight: number | null;
25
+ };
26
+
27
+ /**
28
+ * Custom theme configuration
29
+ *
30
+ * The first non-null value takes precedence
31
+ */
32
+ const customThemeConfig = Facet.define<CustomTheme, CustomTheme>({
33
+ combine(configs: CustomTheme[]) {
34
+ return combineConfig(
35
+ configs,
36
+ {
37
+ fontFamily: null,
38
+ fontSize: null,
39
+ lineHeight: null
40
+ },
41
+ {
42
+ fontFamily: (a, b) => a ?? b,
43
+ fontSize: (a, b) => a ?? b,
44
+ lineHeight: (a, b) => a ?? b
45
+ }
46
+ );
47
+ }
48
+ });
49
+
50
+ function setEditorStyle(view: EditorView): Record<string, string> | null {
51
+ const { fontFamily, fontSize, lineHeight } =
52
+ view.state.facet(customThemeConfig);
53
+
54
+ let style = '';
55
+ if (fontSize) {
56
+ style += `font-size: ${fontSize}px !important;`;
57
+ }
58
+ if (fontFamily) {
59
+ style += `font-family: ${fontFamily} !important;`;
60
+ }
61
+ if (lineHeight) {
62
+ style += `line-height: ${lineHeight.toString()} !important`;
63
+ }
64
+
65
+ return { style: style };
66
+ }
67
+
68
+ /**
69
+ * Get the extension to customize an editor theme.
70
+ *
71
+ * @param config Theme customization
72
+ * @returns Editor extension
73
+ */
74
+ export function customTheme(config: CustomTheme): Extension {
75
+ return [
76
+ customThemeConfig.of(config),
77
+ EditorView.editorAttributes.of(setEditorStyle)
78
+ ];
79
+ }
@@ -0,0 +1,8 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ export * from './customStyle';
7
+ export * from './rulers';
8
+ export * from './ybinding';
@@ -0,0 +1,112 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ // Inspired by https://discuss.codemirror.net/t/how-to-implement-ruler/4616/
7
+
8
+ import { Extension, Facet } from '@codemirror/state';
9
+ import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
10
+ import { JSONExt } from '@lumino/coreutils';
11
+
12
+ const RULERS_CLASSNAME = 'cm-rulers';
13
+
14
+ /**
15
+ * Rulers style
16
+ */
17
+ const baseTheme = EditorView.baseTheme({
18
+ [`.${RULERS_CLASSNAME}`]: { borderRight: '1px dotted gray', opacity: 0.7 }
19
+ });
20
+
21
+ /**
22
+ * Rulers facet
23
+ */
24
+ const rulerConfig = Facet.define<number[], number[]>({
25
+ // Merge all unique values
26
+ combine(value) {
27
+ const final = value.reduce(
28
+ (agg, arr) =>
29
+ agg.concat(
30
+ // Check value is not in aggregate nor multiple time in the array.
31
+ arr.filter((v, idx) => !agg.includes(v) && idx == arr.lastIndexOf(v))
32
+ ),
33
+ []
34
+ );
35
+ return final;
36
+ }
37
+ });
38
+
39
+ /**
40
+ * View plugin displaying the rulers
41
+ */
42
+ const plugin = ViewPlugin.fromClass(
43
+ class {
44
+ constructor(view: EditorView) {
45
+ this.rulersContainer = view.dom.appendChild(
46
+ document.createElement('div')
47
+ );
48
+ this.rulersContainer.style.cssText = `
49
+ position: absolute;
50
+ left: 0;
51
+ top: 0;
52
+ width: 100%;
53
+ height: 100%;
54
+ pointer-events: none;
55
+ overflow: hidden;
56
+ `;
57
+
58
+ const defaultCharacterWidth = view.defaultCharacterWidth;
59
+ const widths = view.state.facet(rulerConfig);
60
+ this.rulers = widths.map(width => {
61
+ const ruler = this.rulersContainer.appendChild(
62
+ document.createElement('div')
63
+ );
64
+ ruler.classList.add(RULERS_CLASSNAME);
65
+ ruler.style.cssText = `
66
+ position: absolute;
67
+ left: ${width * defaultCharacterWidth}px;
68
+ height: 100%;
69
+ `;
70
+ // FIXME: This should be equal to the amount of padding on a line.
71
+ // This value should be extracted from CodeMirror rather than hardcoded.
72
+ ruler.style.width = '6px';
73
+
74
+ return ruler;
75
+ });
76
+ }
77
+
78
+ update(update: ViewUpdate) {
79
+ const widths = update.view.state.facet(rulerConfig);
80
+
81
+ if (
82
+ update.viewportChanged ||
83
+ !JSONExt.deepEqual(widths, update.startState.facet(rulerConfig))
84
+ ) {
85
+ const defaultCharacterWidth = update.view.defaultCharacterWidth;
86
+ this.rulers.forEach((ruler, rulerIdx) => {
87
+ ruler.style.left = `${widths[rulerIdx] * defaultCharacterWidth}px`;
88
+ });
89
+ }
90
+ }
91
+
92
+ destroy() {
93
+ this.rulers.forEach(ruler => {
94
+ ruler.remove();
95
+ });
96
+ this.rulersContainer.remove();
97
+ }
98
+
99
+ rulersContainer: HTMLDivElement;
100
+ rulers: HTMLDivElement[];
101
+ }
102
+ );
103
+
104
+ /**
105
+ * Extension for CodeMirror 6 displaying rulers.
106
+ *
107
+ * @param value Rulers position
108
+ * @returns CodeMirror 6 extension
109
+ */
110
+ export function rulers(value: number[]): Extension {
111
+ return [baseTheme, rulerConfig.of(value), plugin];
112
+ }
@@ -0,0 +1,267 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ *
5
+ * Binding for yjs and codemirror
6
+ *
7
+ * It is a simplification of https://github.com/yjs/y-codemirror.next
8
+ * licensed under MIT License by Kevin Jahns
9
+ */
10
+ import {
11
+ Annotation,
12
+ EditorSelection,
13
+ Extension,
14
+ Facet,
15
+ SelectionRange
16
+ } from '@codemirror/state';
17
+ import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
18
+ import type { RelativePosition, Text, Transaction, YTextEvent } from 'yjs';
19
+ import {
20
+ createAbsolutePositionFromRelativePosition,
21
+ createRelativePositionFromJSON,
22
+ createRelativePositionFromTypeIndex,
23
+ relativePositionToJSON
24
+ } from 'yjs';
25
+
26
+ export type ID = {
27
+ client: number;
28
+ clock: number;
29
+ };
30
+
31
+ export type Position = {
32
+ type: ID | null;
33
+ tname: string | null;
34
+ item: ID | null;
35
+ assoc: number;
36
+ };
37
+
38
+ export type Range = {
39
+ yanchor: Position;
40
+ yhead: Position;
41
+ };
42
+
43
+ /**
44
+ * Defines a range on text using relative positions that can be transformed back to
45
+ * absolute positions. (https://docs.yjs.dev/api/relative-positions)
46
+ */
47
+ export class YRange {
48
+ /**
49
+ * @param yanchor
50
+ * @param yhead
51
+ */
52
+ constructor(
53
+ public yanchor: RelativePosition,
54
+ public yhead: RelativePosition
55
+ ) {}
56
+
57
+ /**
58
+ * Convert the position to JSON
59
+ */
60
+ toJSON(): Range {
61
+ return {
62
+ yanchor: relativePositionToJSON(this.yanchor),
63
+ yhead: relativePositionToJSON(this.yhead)
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Convert a JSON range to a YRange
69
+ * @param json Range to convert
70
+ * @return The range as YRange
71
+ */
72
+ static fromJSON(json: Range): YRange {
73
+ return new YRange(
74
+ createRelativePositionFromJSON(json.yanchor),
75
+ createRelativePositionFromJSON(json.yhead)
76
+ );
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Yjs binding configuration
82
+ */
83
+ export class YSyncConfig {
84
+ /**
85
+ * Create a new binding configuration
86
+ *
87
+ * @param ytext Yjs text to synchronize
88
+ */
89
+ constructor(public ytext: Text) {}
90
+
91
+ /**
92
+ * Helper function to transform an absolute index position to a Yjs-based relative position
93
+ * (https://docs.yjs.dev/api/relative-positions).
94
+ *
95
+ * A relative position can be transformed back to an absolute position even after the document has changed. The position is
96
+ * automatically adapted. This does not require any position transformations. Relative positions are computed based on
97
+ * the internal Yjs document model. Peers that share content through Yjs are guaranteed that their positions will always
98
+ * synced up when using relative positions.
99
+ *
100
+ * ```js
101
+ * import { ySyncFacet } from 'y-codemirror'
102
+ *
103
+ * ..
104
+ * const ysync = view.state.facet(ySyncFacet)
105
+ * // transform an absolute index position to a ypos
106
+ * const ypos = ysync.getYPos(3)
107
+ * // transform the ypos back to an absolute position
108
+ * ysync.fromYPos(ypos) // => 3
109
+ * ```
110
+ *
111
+ * It cannot be guaranteed that absolute index positions can be synced up between peers.
112
+ * This might lead to undesired behavior when implementing features that require that all peers see the
113
+ * same marked range (e.g. a comment plugin).
114
+ *
115
+ * @param pos
116
+ * @param assoc
117
+ */
118
+ toYPos(pos: number, assoc = 0): RelativePosition {
119
+ return createRelativePositionFromTypeIndex(this.ytext, pos, assoc);
120
+ }
121
+
122
+ /**
123
+ * @param rpos
124
+ */
125
+ fromYPos(rpos: RelativePosition | Record<string, any>) {
126
+ const pos = createAbsolutePositionFromRelativePosition(
127
+ createRelativePositionFromJSON(rpos),
128
+ this.ytext.doc!
129
+ );
130
+ if (pos == null || pos.type !== this.ytext) {
131
+ throw new Error(
132
+ '[y-codemirror] The position you want to retrieve was created by a different document'
133
+ );
134
+ }
135
+ return {
136
+ pos: pos.index,
137
+ assoc: pos.assoc
138
+ };
139
+ }
140
+
141
+ /**
142
+ * @param range
143
+ * @return
144
+ */
145
+ toYRange(range: SelectionRange): YRange {
146
+ const assoc = range.assoc;
147
+ const yanchor = this.toYPos(range.anchor, assoc);
148
+ const yhead = this.toYPos(range.head, assoc);
149
+ return new YRange(yanchor, yhead);
150
+ }
151
+
152
+ /**
153
+ * @param yrange
154
+ */
155
+ fromYRange(yrange: YRange): SelectionRange {
156
+ const anchor = this.fromYPos(yrange.yanchor);
157
+ const head = this.fromYPos(yrange.yhead);
158
+ if (anchor.pos === head.pos) {
159
+ return EditorSelection.cursor(head.pos, head.assoc);
160
+ }
161
+ return EditorSelection.range(anchor.pos, head.pos);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Yjs binding facet
167
+ */
168
+ export const ySyncFacet = Facet.define<YSyncConfig, YSyncConfig>({
169
+ combine(inputs) {
170
+ return inputs[inputs.length - 1];
171
+ }
172
+ });
173
+
174
+ /**
175
+ * Yjs binding annotation
176
+ *
177
+ * It is used to track the origin of the document changes
178
+ */
179
+ export const ySyncAnnotation = Annotation.define<YSyncConfig>();
180
+
181
+ /**
182
+ * Yjs binding view plugin to synchronize the
183
+ * editor state with the Yjs document.
184
+ */
185
+ export const ySync = ViewPlugin.fromClass(
186
+ class {
187
+ constructor(view: EditorView) {
188
+ this.conf = view.state.facet(ySyncFacet);
189
+ this._observer = (event: YTextEvent, tr: Transaction) => {
190
+ if (tr.origin !== this.conf) {
191
+ const delta = event.delta;
192
+ const changes: any[] = [];
193
+ let pos = 0;
194
+ for (let i = 0; i < delta.length; i++) {
195
+ const d = delta[i];
196
+ if (d.insert != null) {
197
+ changes.push({ from: pos, to: pos, insert: d.insert });
198
+ } else if (d.delete != null) {
199
+ changes.push({ from: pos, to: pos + d.delete, insert: '' });
200
+ pos += d.delete;
201
+ } else {
202
+ pos += d.retain ?? 0;
203
+ }
204
+ }
205
+ view.dispatch({
206
+ changes,
207
+ // Specified the changes origin to not loop when synchronizing
208
+ annotations: [ySyncAnnotation.of(this.conf)]
209
+ });
210
+ }
211
+ };
212
+ this._ytext = this.conf.ytext;
213
+ this._ytext.observe(this._observer);
214
+ }
215
+
216
+ update(update: ViewUpdate) {
217
+ if (
218
+ !update.docChanged ||
219
+ (update.transactions.length > 0 &&
220
+ update.transactions[0].annotation(ySyncAnnotation) === this.conf)
221
+ ) {
222
+ return;
223
+ }
224
+
225
+ const ytext = this.conf.ytext;
226
+ ytext.doc!.transact(() => {
227
+ /**
228
+ * This variable adjusts the fromA position to the current position in the Y.Text type.
229
+ */
230
+ let adj = 0;
231
+ update.changes.iterChanges((fromA, toA, fromB, toB, insert) => {
232
+ const insertText = insert.sliceString(0, insert.length, '\n');
233
+ if (fromA !== toA) {
234
+ ytext.delete(fromA + adj, toA - fromA);
235
+ }
236
+ if (insertText.length > 0) {
237
+ ytext.insert(fromA + adj, insertText);
238
+ }
239
+ adj += insertText.length - (toA - fromA);
240
+ });
241
+ // Set the configuration as origin to not loop when synchronizing
242
+ }, this.conf);
243
+ }
244
+
245
+ destroy() {
246
+ this._ytext.unobserve(this._observer);
247
+ }
248
+
249
+ conf: YSyncConfig;
250
+ _observer: (event: YTextEvent, tr: Transaction) => void;
251
+ _ytext: Text;
252
+ }
253
+ );
254
+
255
+ /**
256
+ * Extension for CodeMirror 6 binding the Yjs text (source of truth)
257
+ * and the editor state.
258
+ *
259
+ * @param ytext Yjs text to bind
260
+ * @returns CodeMirror 6 extension
261
+ */
262
+ export function ybinding(ytext: Text): Extension {
263
+ const ySyncConfig = new YSyncConfig(ytext);
264
+ // We don't need the undo manager extension as in y-codemirror.next
265
+ // because we deal with undo/redo with our own keyboard shortcut mechanism.
266
+ return [ySyncFacet.of(ySyncConfig), ySync];
267
+ }
package/src/factory.ts ADDED
@@ -0,0 +1,100 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { CodeEditor, IEditorFactoryService } from '@jupyterlab/codeeditor';
5
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
6
+ import { EditorExtensionRegistry } from './extension';
7
+ import { CodeMirrorEditor } from './editor';
8
+ import { EditorLanguageRegistry } from './language';
9
+ import {
10
+ IEditorExtensionFactory,
11
+ IEditorExtensionRegistry,
12
+ IEditorFactoryOptions,
13
+ IEditorLanguageRegistry
14
+ } from './token';
15
+ import { EditorView, keymap } from '@codemirror/view';
16
+ import { searchKeymap } from '@codemirror/search';
17
+
18
+ /**
19
+ * CodeMirror editor factory.
20
+ */
21
+ export class CodeMirrorEditorFactory implements IEditorFactoryService {
22
+ /**
23
+ * Construct an IEditorFactoryService for CodeMirrorEditors.
24
+ */
25
+ constructor(options: IEditorFactoryOptions = {}) {
26
+ this.languages = options.languages ?? new EditorLanguageRegistry();
27
+ this.extensions = options.extensions ?? new EditorExtensionRegistry();
28
+ this.translator = options.translator ?? nullTranslator;
29
+ this.inlineCodeMirrorConfig = {};
30
+ this.documentCodeMirrorConfig = {
31
+ lineNumbers: true,
32
+ scrollPastEnd: true
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Create a new editor for inline code.
38
+ */
39
+ readonly newInlineEditor = (
40
+ options: CodeEditor.IOptions
41
+ ): CodeMirrorEditor => {
42
+ options.host.dataset.type = 'inline';
43
+ return this.newEditor({
44
+ ...options,
45
+ config: { ...this.inlineCodeMirrorConfig, ...(options.config || {}) },
46
+ inline: true,
47
+ // FIXME the search keymap should be added in the search plugin
48
+ extensions: [keymap.of(searchKeymap)].concat(options.extensions ?? [])
49
+ });
50
+ };
51
+
52
+ /**
53
+ * Create a new editor for a full document.
54
+ */
55
+ readonly newDocumentEditor = (
56
+ options: CodeEditor.IOptions
57
+ ): CodeMirrorEditor => {
58
+ options.host.dataset.type = 'document';
59
+ return this.newEditor({
60
+ ...options,
61
+ config: { ...this.documentCodeMirrorConfig, ...(options.config ?? {}) },
62
+ inline: false,
63
+ extensions: [
64
+ keymap.of([
65
+ {
66
+ key: 'Shift-Enter',
67
+ run: (target: EditorView) => {
68
+ return true;
69
+ }
70
+ }
71
+ ])
72
+ ].concat(options.extensions ?? [])
73
+ });
74
+ };
75
+
76
+ /**
77
+ * Create a new editor
78
+ *
79
+ * @param options Editor options
80
+ * @returns The editor
81
+ */
82
+ protected newEditor(
83
+ options: CodeEditor.IOptions & IEditorExtensionFactory.IOptions
84
+ ): CodeMirrorEditor {
85
+ const editor = new CodeMirrorEditor({
86
+ extensionsRegistry: this.extensions,
87
+ languages: this.languages,
88
+ translator: this.translator,
89
+ ...options
90
+ });
91
+
92
+ return editor;
93
+ }
94
+
95
+ protected extensions: IEditorExtensionRegistry;
96
+ protected languages: IEditorLanguageRegistry;
97
+ protected translator: ITranslator;
98
+ protected inlineCodeMirrorConfig: Record<string, any>;
99
+ protected documentCodeMirrorConfig: Record<string, any>;
100
+ }
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+ /**
4
+ * @packageDocumentation
5
+ * @module codemirror
6
+ */
7
+
8
+ export * from './commands';
9
+ export * from './editor';
10
+ export * from './extension';
11
+ export * from './extensions';
12
+ export * from './factory';
13
+ export * from './language';
14
+ export * from './mimetype';
15
+ export * from './searchprovider';
16
+ export * from './theme';
17
+ export * from './token';