@difizen/libro-code-editor 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.
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/es/code-editor-view.d.ts +101 -0
- package/es/code-editor-view.d.ts.map +1 -0
- package/es/code-editor-view.js +339 -0
- package/es/code-editor.d.ts +497 -0
- package/es/code-editor.d.ts.map +1 -0
- package/es/code-editor.js +99 -0
- package/es/completer/completer-protocol.d.ts +210 -0
- package/es/completer/completer-protocol.d.ts.map +1 -0
- package/es/completer/completer-protocol.js +34 -0
- package/es/index.d.ts +6 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +5 -0
- package/es/mimetype.d.ts +33 -0
- package/es/mimetype.d.ts.map +1 -0
- package/es/mimetype.js +8 -0
- package/es/model.d.ts +80 -0
- package/es/model.d.ts.map +1 -0
- package/es/model.js +110 -0
- package/es/module.d.ts +3 -0
- package/es/module.d.ts.map +1 -0
- package/es/module.js +4 -0
- package/package.json +61 -0
- package/src/code-editor-view.tsx +345 -0
- package/src/code-editor.ts +667 -0
- package/src/completer/completer-protocol.ts +259 -0
- package/src/index.ts +5 -0
- package/src/mimetype.ts +34 -0
- package/src/model.ts +120 -0
- package/src/module.ts +6 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { getOrigin, prop } from '@difizen/mana-app';
|
|
2
|
+
import { BaseView, view, ViewOption } from '@difizen/mana-app';
|
|
3
|
+
import { inject, transient } from '@difizen/mana-app';
|
|
4
|
+
import { Deferred, Emitter } from '@difizen/mana-app';
|
|
5
|
+
import { forwardRef, memo } from 'react';
|
|
6
|
+
|
|
7
|
+
import type { IEditor } from './code-editor.js';
|
|
8
|
+
import type {
|
|
9
|
+
CodeEditorFactory,
|
|
10
|
+
ICoordinate,
|
|
11
|
+
IEditorConfig,
|
|
12
|
+
IEditorSelectionStyle,
|
|
13
|
+
CompletionProvider,
|
|
14
|
+
TooltipProvider,
|
|
15
|
+
LSPProvider,
|
|
16
|
+
} from './code-editor.js';
|
|
17
|
+
import type { IModel } from './model.js';
|
|
18
|
+
|
|
19
|
+
export const CodeEditorRender = memo(
|
|
20
|
+
forwardRef<HTMLDivElement>((_props, ref) => {
|
|
21
|
+
return <div ref={ref} />;
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The class name added to an editor widget that has a primary selection.
|
|
27
|
+
*/
|
|
28
|
+
const HAS_SELECTION_CLASS = 'jp-mod-has-primary-selection';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The class name added to an editor widget that has a cursor/selection
|
|
32
|
+
* within the whitespace at the beginning of a line
|
|
33
|
+
*/
|
|
34
|
+
const HAS_IN_LEADING_WHITESPACE_CLASS = 'jp-mod-in-leading-whitespace';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A class used to indicate a drop target.
|
|
38
|
+
*/
|
|
39
|
+
const DROP_TARGET_CLASS = 'jp-mod-dropTarget';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* RegExp to test for leading whitespace
|
|
43
|
+
*/
|
|
44
|
+
const leadingWhitespaceRe = /^\s+$/;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A widget which hosts a code editor.
|
|
48
|
+
*/
|
|
49
|
+
@transient()
|
|
50
|
+
@view('code-editor-view')
|
|
51
|
+
export class CodeEditorView extends BaseView {
|
|
52
|
+
override view = CodeEditorRender;
|
|
53
|
+
|
|
54
|
+
protected classlist: string[] = [];
|
|
55
|
+
|
|
56
|
+
protected options: CodeEditorViewOptions;
|
|
57
|
+
|
|
58
|
+
protected modalChangeEmitter = new Emitter();
|
|
59
|
+
|
|
60
|
+
get onModalChange() {
|
|
61
|
+
return this.modalChangeEmitter.event;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the editor wrapped by the widget.
|
|
66
|
+
*/
|
|
67
|
+
@prop()
|
|
68
|
+
editor: IEditor | undefined;
|
|
69
|
+
protected editorReadyDeferred: Deferred<void> = new Deferred<void>();
|
|
70
|
+
get editorReady() {
|
|
71
|
+
return this.editorReadyDeferred.promise;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Construct a new code editor widget.
|
|
75
|
+
*/
|
|
76
|
+
constructor(@inject(ViewOption) options: CodeEditorViewOptions) {
|
|
77
|
+
super();
|
|
78
|
+
this.options = options;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override onViewMount() {
|
|
82
|
+
const node = this.container?.current;
|
|
83
|
+
if (node) {
|
|
84
|
+
this.editor = this.options.factory({
|
|
85
|
+
host: node,
|
|
86
|
+
model: this.options.model,
|
|
87
|
+
uuid: this.options.uuid,
|
|
88
|
+
config: this.options.config,
|
|
89
|
+
selectionStyle: this.options.selectionStyle,
|
|
90
|
+
tooltipProvider: this.options.tooltipProvider,
|
|
91
|
+
completionProvider: this.options.completionProvider,
|
|
92
|
+
lspProvider: this.options.lspProvider,
|
|
93
|
+
});
|
|
94
|
+
this.editorReadyDeferred.resolve();
|
|
95
|
+
this.editor.onModalChange((val) => this.modalChangeEmitter.fire(val));
|
|
96
|
+
// this.editor.model.selections.changed(this._onSelectionsChanged);
|
|
97
|
+
|
|
98
|
+
if (this.options.autoFocus) {
|
|
99
|
+
getOrigin(this.editor).focus();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
node.addEventListener('focus', this.onViewActive);
|
|
103
|
+
node.addEventListener('dragenter', this._evtDragEnter);
|
|
104
|
+
node.addEventListener('dragleave', this._evtDragLeave);
|
|
105
|
+
node.addEventListener('dragover', this._evtDragOver);
|
|
106
|
+
node.addEventListener('drop', this._evtDrop);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
override onViewUnmount() {
|
|
111
|
+
const node = this.container?.current;
|
|
112
|
+
if (node) {
|
|
113
|
+
node.removeEventListener('focus', this.onViewActive);
|
|
114
|
+
node.removeEventListener('dragenter', this._evtDragEnter);
|
|
115
|
+
node.removeEventListener('dragleave', this._evtDragLeave);
|
|
116
|
+
node.removeEventListener('dragover', this._evtDragOver);
|
|
117
|
+
node.removeEventListener('drop', this._evtDrop);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get the model used by the widget.
|
|
123
|
+
*/
|
|
124
|
+
get model(): IModel | undefined {
|
|
125
|
+
return this.editor?.model;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Dispose of the resources held by the widget.
|
|
130
|
+
*/
|
|
131
|
+
override dispose(): void {
|
|
132
|
+
if (this.isDisposed) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
super.dispose();
|
|
136
|
+
this.editor?.dispose();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
protected onViewActive = (): void => {
|
|
140
|
+
this.editor?.focus();
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A message handler invoked on a `'resize'` message.
|
|
145
|
+
*/
|
|
146
|
+
protected onResize(): void {
|
|
147
|
+
if (this.isVisible) {
|
|
148
|
+
this.editor?.resizeToFit();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
addClass(classname: string) {
|
|
153
|
+
this.classlist.push(classname);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
removeClass(classname: string) {
|
|
157
|
+
const index = this.classlist.indexOf(classname);
|
|
158
|
+
if (index >= 0) {
|
|
159
|
+
this.classlist.splice(index, 1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Handle a change in model selections.
|
|
165
|
+
*/
|
|
166
|
+
protected _onSelectionsChanged(): void {
|
|
167
|
+
if (!this.editor) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const { start, end } = this.editor.getSelection();
|
|
171
|
+
|
|
172
|
+
if (start.column !== end.column || start.line !== end.line) {
|
|
173
|
+
// a selection was made
|
|
174
|
+
this.addClass(HAS_SELECTION_CLASS);
|
|
175
|
+
this.removeClass(HAS_IN_LEADING_WHITESPACE_CLASS);
|
|
176
|
+
} else {
|
|
177
|
+
// the cursor was placed
|
|
178
|
+
this.removeClass(HAS_SELECTION_CLASS);
|
|
179
|
+
|
|
180
|
+
if (
|
|
181
|
+
this.editor.getLine(end.line)!.slice(0, end.column).match(leadingWhitespaceRe)
|
|
182
|
+
) {
|
|
183
|
+
this.addClass(HAS_IN_LEADING_WHITESPACE_CLASS);
|
|
184
|
+
} else {
|
|
185
|
+
this.removeClass(HAS_IN_LEADING_WHITESPACE_CLASS);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Handle the `'lm-dragenter'` event for the widget.
|
|
192
|
+
*/
|
|
193
|
+
protected _evtDragEnter = (event: DragEvent): void => {
|
|
194
|
+
if (!this.editor) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (this.editor.getOption('readOnly') === true) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const data = findTextData(event);
|
|
201
|
+
if (data === undefined) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
event.preventDefault();
|
|
205
|
+
event.stopPropagation();
|
|
206
|
+
this.addClass('jp-mod-dropTarget');
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Handle the `'lm-dragleave'` event for the widget.
|
|
211
|
+
*/
|
|
212
|
+
protected _evtDragLeave = (event: DragEvent): void => {
|
|
213
|
+
if (!this.editor) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.removeClass(DROP_TARGET_CLASS);
|
|
217
|
+
if (this.editor.getOption('readOnly') === true) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const data = findTextData(event);
|
|
221
|
+
if (data === undefined) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
event.stopPropagation();
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Handle the `'lm-dragover'` event for the widget.
|
|
230
|
+
*/
|
|
231
|
+
protected _evtDragOver = (event: DragEvent): void => {
|
|
232
|
+
if (!this.editor) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
this.removeClass(DROP_TARGET_CLASS);
|
|
236
|
+
if (this.editor.getOption('readOnly') === true) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const data = findTextData(event);
|
|
240
|
+
if (data === undefined) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
event.stopPropagation();
|
|
245
|
+
event.dataTransfer!.dropEffect = 'copy';
|
|
246
|
+
this.addClass(DROP_TARGET_CLASS);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Handle the `'lm-drop'` event for the widget.
|
|
251
|
+
*/
|
|
252
|
+
protected _evtDrop = (event: DragEvent): void => {
|
|
253
|
+
if (!this.editor) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (this.editor.getOption('readOnly') === true) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const data = findTextData(event);
|
|
260
|
+
if (data === undefined) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const coordinate = {
|
|
264
|
+
top: event.y,
|
|
265
|
+
bottom: event.y,
|
|
266
|
+
left: event.x,
|
|
267
|
+
right: event.x,
|
|
268
|
+
x: event.x,
|
|
269
|
+
y: event.y,
|
|
270
|
+
width: 0,
|
|
271
|
+
height: 0,
|
|
272
|
+
} as ICoordinate;
|
|
273
|
+
const position = this.editor.getPositionForCoordinate(coordinate);
|
|
274
|
+
if (position === null) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
this.removeClass(DROP_TARGET_CLASS);
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
event.stopPropagation();
|
|
280
|
+
// if (event.proposedAction === 'none') {
|
|
281
|
+
// event.dropAction = 'none';
|
|
282
|
+
// return;
|
|
283
|
+
// }
|
|
284
|
+
// const offset = this.editor.getOffsetAt(position);
|
|
285
|
+
// this.model.value.insert(offset, data);
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The namespace for the `CodeEditorWrapper` statics.
|
|
291
|
+
*/
|
|
292
|
+
/**
|
|
293
|
+
* The options used to initialize a code editor widget.
|
|
294
|
+
*/
|
|
295
|
+
export interface CodeEditorViewOptions {
|
|
296
|
+
/**
|
|
297
|
+
* A code editor factory.
|
|
298
|
+
*
|
|
299
|
+
* #### Notes
|
|
300
|
+
* The widget needs a factory and a model instead of a `CodeEditor.IEditor`
|
|
301
|
+
* object because it needs to provide its own node as the host.
|
|
302
|
+
*/
|
|
303
|
+
factory: CodeEditorFactory;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* The model used to initialize the code editor.
|
|
307
|
+
*/
|
|
308
|
+
model: IModel;
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* The desired uuid for the editor.
|
|
312
|
+
*/
|
|
313
|
+
uuid?: string;
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The configuration options for the editor.
|
|
317
|
+
*/
|
|
318
|
+
config?: Partial<IEditorConfig>;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* The default selection style for the editor.
|
|
322
|
+
*/
|
|
323
|
+
selectionStyle?: IEditorSelectionStyle;
|
|
324
|
+
|
|
325
|
+
tooltipProvider?: TooltipProvider;
|
|
326
|
+
completionProvider?: CompletionProvider;
|
|
327
|
+
lspProvider?: LSPProvider;
|
|
328
|
+
|
|
329
|
+
autoFocus?: boolean;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Given a MimeData instance, extract the first text data, if any.
|
|
334
|
+
*/
|
|
335
|
+
function findTextData(event: DragEvent): string | undefined {
|
|
336
|
+
const items = event.dataTransfer?.items;
|
|
337
|
+
if (!items) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const textTypeItem = Array.from(items).find((t) => t.type.indexOf('text') === 0);
|
|
341
|
+
if (textTypeItem === undefined) {
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
return event.dataTransfer.getData(textTypeItem.type);
|
|
345
|
+
}
|