@jupyterlab/codemirror 4.0.0-alpha.9 → 4.0.0-beta.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.
- package/lib/codemirror-ipythongfm.d.ts +9 -3
- package/lib/codemirror-ipythongfm.js +52 -30
- package/lib/codemirror-ipythongfm.js.map +1 -1
- package/lib/commands.d.ts +13 -0
- package/lib/commands.js +32 -0
- package/lib/commands.js.map +1 -0
- package/lib/editor.d.ts +48 -273
- package/lib/editor.js +220 -819
- package/lib/editor.js.map +1 -1
- package/lib/extension.d.ts +236 -0
- package/lib/extension.js +696 -0
- package/lib/extension.js.map +1 -0
- package/lib/extensions/customStyle.d.ts +25 -0
- package/lib/extensions/customStyle.js +51 -0
- package/lib/extensions/customStyle.js.map +1 -0
- package/lib/extensions/index.d.ts +3 -0
- package/lib/extensions/index.js +8 -0
- package/lib/extensions/index.js.map +1 -0
- package/lib/extensions/rulers.d.ts +8 -0
- package/lib/extensions/rulers.js +85 -0
- package/lib/extensions/rulers.js.map +1 -0
- package/lib/extensions/ybinding.d.ts +129 -0
- package/lib/extensions/ybinding.js +229 -0
- package/lib/extensions/ybinding.js.map +1 -0
- package/lib/factory.d.ts +15 -5
- package/lib/factory.js +53 -24
- package/lib/factory.js.map +1 -1
- package/lib/index.d.ts +7 -19
- package/lib/index.js +7 -23
- package/lib/index.js.map +1 -1
- package/lib/language.d.ts +96 -0
- package/lib/language.js +1687 -0
- package/lib/language.js.map +1 -0
- package/lib/mimetype.d.ts +3 -0
- package/lib/mimetype.js +16 -5
- package/lib/mimetype.js.map +1 -1
- package/lib/searchprovider.d.ts +219 -0
- package/lib/searchprovider.js +640 -0
- package/lib/searchprovider.js.map +1 -0
- package/lib/theme.d.ts +57 -0
- package/lib/theme.js +194 -0
- package/lib/theme.js.map +1 -0
- package/lib/token.d.ts +375 -0
- package/lib/token.js +18 -0
- package/lib/token.js.map +1 -0
- package/package.json +45 -41
- package/src/codemirror-ipythongfm.ts +109 -0
- package/src/commands.ts +34 -0
- package/src/editor.ts +783 -0
- package/src/extension.ts +890 -0
- package/src/extensions/customStyle.ts +79 -0
- package/src/extensions/index.ts +8 -0
- package/src/extensions/rulers.ts +112 -0
- package/src/extensions/ybinding.ts +295 -0
- package/src/factory.ts +100 -0
- package/src/index.ts +17 -0
- package/src/language.ts +1726 -0
- package/src/mimetype.ts +54 -0
- package/src/searchprovider.ts +813 -0
- package/src/theme.ts +219 -0
- package/src/token.ts +436 -0
- package/src/typings.d.ts +13 -0
- package/style/base.css +33 -187
- package/style/index.css +1 -7
- package/style/index.js +1 -7
- package/lib/codemirror-ipython.d.ts +0 -2
- package/lib/codemirror-ipython.js +0 -30
- package/lib/codemirror-ipython.js.map +0 -1
- package/lib/mode.d.ts +0 -82
- package/lib/mode.js +0 -150
- package/lib/mode.js.map +0 -1
- package/lib/syntaxstatus.d.ts +0 -67
- package/lib/syntaxstatus.js +0 -140
- package/lib/syntaxstatus.js.map +0 -1
- package/lib/tokens.d.ts +0 -18
- package/lib/tokens.js +0 -8
- package/lib/tokens.js.map +0 -1
- 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,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,295 @@
|
|
|
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 {
|
|
19
|
+
RelativePosition,
|
|
20
|
+
Text,
|
|
21
|
+
Transaction,
|
|
22
|
+
UndoManager,
|
|
23
|
+
YTextEvent
|
|
24
|
+
} from 'yjs';
|
|
25
|
+
import {
|
|
26
|
+
createAbsolutePositionFromRelativePosition,
|
|
27
|
+
createRelativePositionFromJSON,
|
|
28
|
+
createRelativePositionFromTypeIndex,
|
|
29
|
+
relativePositionToJSON
|
|
30
|
+
} from 'yjs';
|
|
31
|
+
|
|
32
|
+
export type ID = {
|
|
33
|
+
client: number;
|
|
34
|
+
clock: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type Position = {
|
|
38
|
+
type: ID | null;
|
|
39
|
+
tname: string | null;
|
|
40
|
+
item: ID | null;
|
|
41
|
+
assoc: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type Range = {
|
|
45
|
+
yanchor: Position;
|
|
46
|
+
yhead: Position;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Defines a range on text using relative positions that can be transformed back to
|
|
51
|
+
* absolute positions. (https://docs.yjs.dev/api/relative-positions)
|
|
52
|
+
*/
|
|
53
|
+
export class YRange {
|
|
54
|
+
/**
|
|
55
|
+
* @param yanchor
|
|
56
|
+
* @param yhead
|
|
57
|
+
*/
|
|
58
|
+
constructor(
|
|
59
|
+
public yanchor: RelativePosition,
|
|
60
|
+
public yhead: RelativePosition
|
|
61
|
+
) {}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Convert the position to JSON
|
|
65
|
+
*/
|
|
66
|
+
toJSON(): Range {
|
|
67
|
+
return {
|
|
68
|
+
yanchor: relativePositionToJSON(this.yanchor),
|
|
69
|
+
yhead: relativePositionToJSON(this.yhead)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Convert a JSON range to a YRange
|
|
75
|
+
* @param json Range to convert
|
|
76
|
+
* @return The range as YRange
|
|
77
|
+
*/
|
|
78
|
+
static fromJSON(json: Range): YRange {
|
|
79
|
+
return new YRange(
|
|
80
|
+
createRelativePositionFromJSON(json.yanchor),
|
|
81
|
+
createRelativePositionFromJSON(json.yhead)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Yjs binding configuration
|
|
88
|
+
*/
|
|
89
|
+
export class YSyncConfig {
|
|
90
|
+
/**
|
|
91
|
+
* Create a new binding configuration
|
|
92
|
+
*
|
|
93
|
+
* @param ytext Yjs text to synchronize
|
|
94
|
+
*/
|
|
95
|
+
constructor(public ytext: Text) {}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Helper function to transform an absolute index position to a Yjs-based relative position
|
|
99
|
+
* (https://docs.yjs.dev/api/relative-positions).
|
|
100
|
+
*
|
|
101
|
+
* A relative position can be transformed back to an absolute position even after the document has changed. The position is
|
|
102
|
+
* automatically adapted. This does not require any position transformations. Relative positions are computed based on
|
|
103
|
+
* the internal Yjs document model. Peers that share content through Yjs are guaranteed that their positions will always
|
|
104
|
+
* synced up when using relative positions.
|
|
105
|
+
*
|
|
106
|
+
* ```js
|
|
107
|
+
* import { ySyncFacet } from 'y-codemirror'
|
|
108
|
+
*
|
|
109
|
+
* ..
|
|
110
|
+
* const ysync = view.state.facet(ySyncFacet)
|
|
111
|
+
* // transform an absolute index position to a ypos
|
|
112
|
+
* const ypos = ysync.getYPos(3)
|
|
113
|
+
* // transform the ypos back to an absolute position
|
|
114
|
+
* ysync.fromYPos(ypos) // => 3
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* It cannot be guaranteed that absolute index positions can be synced up between peers.
|
|
118
|
+
* This might lead to undesired behavior when implementing features that require that all peers see the
|
|
119
|
+
* same marked range (e.g. a comment plugin).
|
|
120
|
+
*
|
|
121
|
+
* @param pos
|
|
122
|
+
* @param assoc
|
|
123
|
+
*/
|
|
124
|
+
toYPos(pos: number, assoc = 0): RelativePosition {
|
|
125
|
+
return createRelativePositionFromTypeIndex(this.ytext, pos, assoc);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param rpos
|
|
130
|
+
*/
|
|
131
|
+
fromYPos(rpos: RelativePosition | Record<string, any>) {
|
|
132
|
+
const pos = createAbsolutePositionFromRelativePosition(
|
|
133
|
+
createRelativePositionFromJSON(rpos),
|
|
134
|
+
this.ytext.doc!
|
|
135
|
+
);
|
|
136
|
+
if (pos == null || pos.type !== this.ytext) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
'[y-codemirror] The position you want to retrieve was created by a different document'
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
pos: pos.index,
|
|
143
|
+
assoc: pos.assoc
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @param range
|
|
149
|
+
* @return
|
|
150
|
+
*/
|
|
151
|
+
toYRange(range: SelectionRange): YRange {
|
|
152
|
+
const assoc = range.assoc;
|
|
153
|
+
const yanchor = this.toYPos(range.anchor, assoc);
|
|
154
|
+
const yhead = this.toYPos(range.head, assoc);
|
|
155
|
+
return new YRange(yanchor, yhead);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @param yrange
|
|
160
|
+
*/
|
|
161
|
+
fromYRange(yrange: YRange): SelectionRange {
|
|
162
|
+
const anchor = this.fromYPos(yrange.yanchor);
|
|
163
|
+
const head = this.fromYPos(yrange.yhead);
|
|
164
|
+
if (anchor.pos === head.pos) {
|
|
165
|
+
return EditorSelection.cursor(head.pos, head.assoc);
|
|
166
|
+
}
|
|
167
|
+
return EditorSelection.range(anchor.pos, head.pos);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Yjs binding facet
|
|
173
|
+
*/
|
|
174
|
+
export const ySyncFacet = Facet.define<YSyncConfig, YSyncConfig>({
|
|
175
|
+
combine(inputs) {
|
|
176
|
+
return inputs[inputs.length - 1];
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Yjs binding annotation
|
|
182
|
+
*
|
|
183
|
+
* It is used to track the origin of the document changes
|
|
184
|
+
*/
|
|
185
|
+
export const ySyncAnnotation = Annotation.define<YSyncConfig>();
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Yjs binding view plugin to synchronize the
|
|
189
|
+
* editor state with the Yjs document.
|
|
190
|
+
*/
|
|
191
|
+
export const ySync = ViewPlugin.fromClass(
|
|
192
|
+
class {
|
|
193
|
+
constructor(view: EditorView) {
|
|
194
|
+
this.conf = view.state.facet(ySyncFacet);
|
|
195
|
+
this._observer = (event: YTextEvent, tr: Transaction) => {
|
|
196
|
+
if (tr.origin !== this.conf) {
|
|
197
|
+
const delta = event.delta;
|
|
198
|
+
const changes: any[] = [];
|
|
199
|
+
let pos = 0;
|
|
200
|
+
for (let i = 0; i < delta.length; i++) {
|
|
201
|
+
const d = delta[i];
|
|
202
|
+
if (d.insert != null) {
|
|
203
|
+
changes.push({ from: pos, to: pos, insert: d.insert });
|
|
204
|
+
} else if (d.delete != null) {
|
|
205
|
+
changes.push({ from: pos, to: pos + d.delete, insert: '' });
|
|
206
|
+
pos += d.delete;
|
|
207
|
+
} else {
|
|
208
|
+
pos += d.retain ?? 0;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
view.dispatch({
|
|
212
|
+
changes,
|
|
213
|
+
// Specified the changes origin to not loop when synchronizing
|
|
214
|
+
annotations: [ySyncAnnotation.of(this.conf)]
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
this._ytext = this.conf.ytext;
|
|
219
|
+
this._ytext.observe(this._observer);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
update(update: ViewUpdate) {
|
|
223
|
+
if (
|
|
224
|
+
!update.docChanged ||
|
|
225
|
+
(update.transactions.length > 0 &&
|
|
226
|
+
update.transactions[0].annotation(ySyncAnnotation) === this.conf)
|
|
227
|
+
) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const ytext = this.conf.ytext;
|
|
232
|
+
ytext.doc!.transact(() => {
|
|
233
|
+
/**
|
|
234
|
+
* This variable adjusts the fromA position to the current position in the Y.Text type.
|
|
235
|
+
*/
|
|
236
|
+
let adj = 0;
|
|
237
|
+
update.changes.iterChanges((fromA, toA, fromB, toB, insert) => {
|
|
238
|
+
const insertText = insert.sliceString(0, insert.length, '\n');
|
|
239
|
+
if (fromA !== toA) {
|
|
240
|
+
ytext.delete(fromA + adj, toA - fromA);
|
|
241
|
+
}
|
|
242
|
+
if (insertText.length > 0) {
|
|
243
|
+
ytext.insert(fromA + adj, insertText);
|
|
244
|
+
}
|
|
245
|
+
adj += insertText.length - (toA - fromA);
|
|
246
|
+
});
|
|
247
|
+
// Set the configuration as origin to not loop when synchronizing
|
|
248
|
+
}, this.conf);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
destroy() {
|
|
252
|
+
this._ytext.unobserve(this._observer);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
conf: YSyncConfig;
|
|
256
|
+
_observer: (event: YTextEvent, tr: Transaction) => void;
|
|
257
|
+
_ytext: Text;
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Extension for CodeMirror 6 binding the Yjs text (source of truth)
|
|
263
|
+
* and the editor state.
|
|
264
|
+
*
|
|
265
|
+
* @param ytext Yjs text to bind
|
|
266
|
+
* @param undoManager Yjs text undo manager
|
|
267
|
+
* @returns CodeMirror 6 extension
|
|
268
|
+
*/
|
|
269
|
+
export function ybinding({
|
|
270
|
+
ytext,
|
|
271
|
+
undoManager
|
|
272
|
+
}: {
|
|
273
|
+
ytext: Text;
|
|
274
|
+
undoManager?: UndoManager;
|
|
275
|
+
}): Extension {
|
|
276
|
+
const ySyncConfig = new YSyncConfig(ytext);
|
|
277
|
+
// We don't need the undo manager extension as in y-codemirror.next
|
|
278
|
+
// because we deal with undo/redo with our own keyboard shortcut mechanism.
|
|
279
|
+
return [
|
|
280
|
+
ySyncFacet.of(ySyncConfig),
|
|
281
|
+
ySync,
|
|
282
|
+
// We need to add a new origin to the undo manager to ensure text updates
|
|
283
|
+
// are tracked.
|
|
284
|
+
undoManager
|
|
285
|
+
? ViewPlugin.define(() => {
|
|
286
|
+
undoManager.addTrackedOrigin(ySyncConfig);
|
|
287
|
+
return {
|
|
288
|
+
destroy: () => {
|
|
289
|
+
undoManager.removeTrackedOrigin(ySyncConfig);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
})
|
|
293
|
+
: []
|
|
294
|
+
];
|
|
295
|
+
}
|
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';
|