@difizen/libro-code-editor 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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/es/code-editor-info-manager.d.ts +8 -0
  4. package/es/code-editor-info-manager.d.ts.map +1 -0
  5. package/es/code-editor-info-manager.js +32 -0
  6. package/es/code-editor-manager.d.ts +60 -0
  7. package/es/code-editor-manager.d.ts.map +1 -0
  8. package/es/code-editor-manager.js +126 -0
  9. package/es/code-editor-model.d.ts +80 -0
  10. package/es/code-editor-model.d.ts.map +1 -0
  11. package/es/code-editor-model.js +108 -0
  12. package/es/code-editor-protocol.d.ts +475 -0
  13. package/es/code-editor-protocol.d.ts.map +1 -0
  14. package/es/code-editor-protocol.js +113 -0
  15. package/es/code-editor-settings.d.ts +34 -0
  16. package/es/code-editor-settings.d.ts.map +1 -0
  17. package/es/code-editor-settings.js +225 -0
  18. package/es/code-editor-state-manager.d.ts +14 -0
  19. package/es/code-editor-state-manager.d.ts.map +1 -0
  20. package/es/code-editor-state-manager.js +82 -0
  21. package/es/code-editor-view.d.ts +123 -0
  22. package/es/code-editor-view.d.ts.map +1 -0
  23. package/es/code-editor-view.js +420 -0
  24. package/es/index.d.ts +9 -0
  25. package/es/index.d.ts.map +1 -0
  26. package/es/index.js +8 -0
  27. package/es/language-specs.d.ts +38 -0
  28. package/es/language-specs.d.ts.map +1 -0
  29. package/es/language-specs.js +58 -0
  30. package/es/mimetype.d.ts +33 -0
  31. package/es/mimetype.d.ts.map +1 -0
  32. package/es/mimetype.js +11 -0
  33. package/es/module.d.ts +3 -0
  34. package/es/module.d.ts.map +1 -0
  35. package/es/module.js +10 -0
  36. package/package.json +60 -0
  37. package/src/code-editor-info-manager.ts +25 -0
  38. package/src/code-editor-manager.ts +71 -0
  39. package/src/code-editor-model.ts +120 -0
  40. package/src/code-editor-protocol.ts +681 -0
  41. package/src/code-editor-settings.ts +214 -0
  42. package/src/code-editor-state-manager.ts +54 -0
  43. package/src/code-editor-view.tsx +434 -0
  44. package/src/index.spec.ts +10 -0
  45. package/src/index.ts +8 -0
  46. package/src/language-specs.ts +69 -0
  47. package/src/mimetype.ts +37 -0
  48. package/src/module.ts +22 -0
@@ -0,0 +1,214 @@
1
+ import {
2
+ inject,
3
+ singleton,
4
+ ApplicationContribution,
5
+ DisposableCollection,
6
+ Emitter,
7
+ ConfigurationContribution,
8
+ ConfigurationService,
9
+ } from '@difizen/mana-app';
10
+ import type {
11
+ Disposable,
12
+ ConfigurationNode,
13
+ ConfigurationStorage,
14
+ } from '@difizen/mana-app';
15
+ import { l10n } from '@difizen/mana-l10n';
16
+
17
+ import type { IEditorConfig } from './code-editor-protocol.js';
18
+ import { defaultConfig } from './code-editor-protocol.js';
19
+
20
+ declare global {
21
+ interface ObjectConstructor {
22
+ typedKeys<T>(obj: T): (keyof T)[];
23
+ }
24
+ }
25
+ Object.typedKeys = Object.keys as any;
26
+
27
+ const LibroUserSettingStorage: ConfigurationStorage = {
28
+ id: '__libro.user.storage__',
29
+ priority: 100,
30
+ };
31
+
32
+ const FontSize: ConfigurationNode<number> = {
33
+ id: 'libro.user.codeeditor.fontsize',
34
+ description: l10n.t('代码编辑区域字体大小'),
35
+ title: l10n.t('代码字号'),
36
+ type: 'inputnumber',
37
+ defaultValue: defaultConfig.fontSize ?? 13,
38
+ schema: {
39
+ type: 'number',
40
+ },
41
+ storage: LibroUserSettingStorage,
42
+ };
43
+
44
+ const LineHeight: ConfigurationNode<number> = {
45
+ id: 'libro.user.codeeditor.lineheight',
46
+ description: l10n.t('代码编辑区域字体行高'),
47
+ title: l10n.t('代码行高'),
48
+ type: 'inputnumber',
49
+ defaultValue: defaultConfig.lineHeight ?? 20,
50
+ schema: {
51
+ type: 'number',
52
+ },
53
+ storage: LibroUserSettingStorage,
54
+ };
55
+
56
+ const TabSize: ConfigurationNode<number> = {
57
+ id: 'libro.user.codeeditor.tabsize',
58
+ description: l10n.t('tab转换为几个空格大小'),
59
+ title: l10n.t('tab大小'),
60
+ type: 'inputnumber',
61
+ defaultValue: defaultConfig.tabSize ?? 4,
62
+ schema: {
63
+ type: 'number',
64
+ },
65
+ storage: LibroUserSettingStorage,
66
+ };
67
+
68
+ const InsertSpaces: ConfigurationNode<boolean> = {
69
+ id: 'libro.user.codeeditor.insertspaces',
70
+ description: l10n.t('输入tab是否转换为空格'),
71
+ title: l10n.t('tab转空格'),
72
+ type: 'checkbox',
73
+ defaultValue: defaultConfig.insertSpaces,
74
+ schema: {
75
+ type: 'boolean',
76
+ },
77
+ storage: LibroUserSettingStorage,
78
+ };
79
+
80
+ const LineWarp: ConfigurationNode<'wordWrapColumn' | 'off' | 'on' | 'bounded'> = {
81
+ id: 'libro.user.codeeditor.linewarp',
82
+ description: l10n.t(`自动换行策略:
83
+ - "off", lines will never wrap.
84
+ - "on", lines will wrap at the viewport border.
85
+ - "wordWrapColumn", lines will wrap at 'wordWrapColumn'.
86
+ - "bounded", lines will wrap at minimum between viewport width and wordWrapColumn.`),
87
+ title: l10n.t('自动换行'),
88
+ type: 'select',
89
+ defaultValue: defaultConfig.lineWrap,
90
+ schema: {
91
+ type: 'string',
92
+ enum: ['off', 'on', 'wordWrapColumn', 'bounded'],
93
+ },
94
+ storage: LibroUserSettingStorage,
95
+ };
96
+
97
+ const WordWrapColumn: ConfigurationNode<number> = {
98
+ id: 'libro.user.codeeditor.wordWrapColumn',
99
+ description: l10n.t('开启自动换行后,自动换行的列数'),
100
+ title: l10n.t('自动换行列数'),
101
+ type: 'inputnumber',
102
+ defaultValue: defaultConfig.wordWrapColumn,
103
+ schema: {
104
+ type: 'number',
105
+ },
106
+ storage: LibroUserSettingStorage,
107
+ };
108
+
109
+ const LSPEnabled: ConfigurationNode<boolean> = {
110
+ id: 'libro.user.codeeditor.lspenabled',
111
+ description: l10n.t(
112
+ '开启语言服务后,编辑器能提供更多辅助编码能力,包括:自动提示、代码诊断、hover提示、格式化、代码跳转、重命名等等',
113
+ ),
114
+ title: l10n.t('开启语言服务'),
115
+ type: 'checkbox',
116
+ defaultValue: defaultConfig.lspEnabled,
117
+ schema: {
118
+ type: 'boolean',
119
+ },
120
+ storage: LibroUserSettingStorage,
121
+ };
122
+
123
+ export const CodeEditorSetting: {
124
+ [key in keyof IEditorConfig]?: ConfigurationNode<any>;
125
+ } = {
126
+ fontSize: FontSize,
127
+ tabSize: TabSize,
128
+ insertSpaces: InsertSpaces,
129
+ lineHeight: LineHeight,
130
+ lineWrap: LineWarp,
131
+ wordWrapColumn: WordWrapColumn,
132
+ lspEnabled: LSPEnabled,
133
+ };
134
+
135
+ @singleton({ contrib: [ConfigurationContribution, ApplicationContribution] })
136
+ export class CodeEditorSettings
137
+ implements ConfigurationContribution, ApplicationContribution, Disposable
138
+ {
139
+ protected readonly configurationService: ConfigurationService;
140
+
141
+ protected codeEditorSettingsChangeEmitter = new Emitter<{
142
+ key: keyof IEditorConfig;
143
+ value: any;
144
+ }>();
145
+
146
+ onCodeEditorSettingsChange = this.codeEditorSettingsChangeEmitter.event;
147
+
148
+ protected toDispose = new DisposableCollection();
149
+
150
+ protected useSettings: Partial<IEditorConfig> | undefined;
151
+
152
+ constructor(
153
+ @inject(ConfigurationService)
154
+ configurationService: ConfigurationService,
155
+ ) {
156
+ this.configurationService = configurationService;
157
+ }
158
+ registerConfigurations() {
159
+ return [
160
+ FontSize,
161
+ TabSize,
162
+ InsertSpaces,
163
+ LineHeight,
164
+ LineWarp,
165
+ WordWrapColumn,
166
+ LSPEnabled,
167
+ ];
168
+ }
169
+
170
+ async onStart() {
171
+ this.useSettings = await this.fetchUserEditorSettings();
172
+ this.handleEditorSettingsChange();
173
+ }
174
+ getUserEditorSettings(): Partial<IEditorConfig> | undefined {
175
+ return this.useSettings;
176
+ }
177
+
178
+ protected async fetchUserEditorSettings(): Promise<Partial<IEditorConfig>> {
179
+ const result: Partial<IEditorConfig> = {};
180
+ Object.typedKeys(CodeEditorSetting).forEach(async (key) => {
181
+ result[key] = await this.configurationService.get(CodeEditorSetting[key]!);
182
+ });
183
+ return result;
184
+ }
185
+
186
+ protected handleEditorSettingsChange() {
187
+ this.toDispose.push(
188
+ this.configurationService.onConfigurationValueChange((e) => {
189
+ // const ids = Object.values(CodeEditorSetting).map(item => item.id);
190
+ const match = Object.entries(CodeEditorSetting).find(
191
+ (item) => item[1].id === e.key,
192
+ );
193
+ if (match) {
194
+ this.codeEditorSettingsChangeEmitter.fire({
195
+ key: match[0] as any,
196
+ value: e.value,
197
+ });
198
+ }
199
+ }),
200
+ );
201
+ }
202
+
203
+ protected isDisposed = false;
204
+ get disposed() {
205
+ return this.isDisposed;
206
+ }
207
+ dispose() {
208
+ if (this.disposed) {
209
+ return;
210
+ }
211
+ this.toDispose.dispose();
212
+ this.isDisposed = true;
213
+ }
214
+ }
@@ -0,0 +1,54 @@
1
+ import type { Contribution } from '@difizen/mana-app';
2
+ import { Priority } from '@difizen/mana-app';
3
+ import { contrib } from '@difizen/mana-app';
4
+ import { singleton } from '@difizen/mana-app';
5
+
6
+ import type { IModel } from './code-editor-model.js';
7
+ import type { EditorState, IEditorStateOptions } from './code-editor-protocol.js';
8
+ import { CodeEditorContribution } from './code-editor-protocol.js';
9
+
10
+ @singleton()
11
+ export class CodeEditorStateManager {
12
+ protected readonly codeEditorProvider: Contribution.Provider<CodeEditorContribution>;
13
+ protected stateCache: Map<string, EditorState> = new Map();
14
+
15
+ constructor(
16
+ @contrib(CodeEditorContribution)
17
+ codeEditorProvider: Contribution.Provider<CodeEditorContribution>,
18
+ ) {
19
+ this.codeEditorProvider = codeEditorProvider;
20
+ }
21
+
22
+ protected findCodeEditorProvider(model: IModel) {
23
+ const prioritized = Priority.sortSync(
24
+ this.codeEditorProvider.getContributions(),
25
+ (contribution) => contribution.canHandle(model.mimeType),
26
+ );
27
+ const sorted = prioritized.map((c) => c.value);
28
+ return sorted[0];
29
+ }
30
+
31
+ async getOrCreateEditorState(option: IEditorStateOptions): Promise<EditorState> {
32
+ if (this.stateCache.has(option.uuid)) {
33
+ const state = this.stateCache.get(option.uuid)!;
34
+ return state;
35
+ }
36
+ const factory = this.findCodeEditorProvider(option.model)?.stateFactory;
37
+ if (!factory) {
38
+ throw new Error(
39
+ `no code editor state factory registered for mimetype: ${option.model.mimeType}`,
40
+ );
41
+ }
42
+ const state = factory(option);
43
+ this.stateCache.set(option.uuid, state);
44
+ return state;
45
+ }
46
+
47
+ updateEditorState(id: string, state: EditorState) {
48
+ this.stateCache.set(id, state);
49
+ }
50
+
51
+ removeEditorState(id: string) {
52
+ this.stateCache.delete(id);
53
+ }
54
+ }
@@ -0,0 +1,434 @@
1
+ import {
2
+ inject,
3
+ transient,
4
+ Emitter,
5
+ BaseView,
6
+ ThemeService,
7
+ view,
8
+ ViewOption,
9
+ } from '@difizen/mana-app';
10
+ import { forwardRef, memo } from 'react';
11
+
12
+ import { CodeEditorInfoManager } from './code-editor-info-manager.js';
13
+ import type { IModel } from './code-editor-model.js';
14
+ import type {
15
+ CompletionProvider,
16
+ ICoordinate,
17
+ IEditorConfig,
18
+ IEditor,
19
+ IEditorSelectionStyle,
20
+ TooltipProvider,
21
+ CodeEditorFactory,
22
+ } from './code-editor-protocol.js';
23
+ import { CodeEditorSettings } from './code-editor-settings.js';
24
+ import { CodeEditorStateManager } from './code-editor-state-manager.js';
25
+
26
+ export const CodeEditorRender = memo(
27
+ forwardRef<HTMLDivElement>((props, ref) => {
28
+ return <div tabIndex={0} ref={ref} />;
29
+ }),
30
+ );
31
+
32
+ /**
33
+ * The class name added to an editor widget that has a primary selection.
34
+ */
35
+ const HAS_SELECTION_CLASS = 'jp-mod-has-primary-selection';
36
+
37
+ /**
38
+ * The class name added to an editor widget that has a cursor/selection
39
+ * within the whitespace at the beginning of a line
40
+ */
41
+ const HAS_IN_LEADING_WHITESPACE_CLASS = 'jp-mod-in-leading-whitespace';
42
+
43
+ /**
44
+ * A class used to indicate a drop target.
45
+ */
46
+ const DROP_TARGET_CLASS = 'jp-mod-dropTarget';
47
+
48
+ /**
49
+ * RegExp to test for leading whitespace
50
+ */
51
+ const leadingWhitespaceRe = /^\s+$/;
52
+
53
+ export type CodeEditorViewStatus = 'init' | 'ready' | 'disposed';
54
+
55
+ /**
56
+ * A widget which hosts a code editor.
57
+ */
58
+ @transient()
59
+ @view('code-editor-view')
60
+ export class CodeEditorView extends BaseView {
61
+ @inject(ThemeService) protected readonly themeService: ThemeService;
62
+ @inject(CodeEditorSettings) protected readonly codeEditorSettings: CodeEditorSettings;
63
+ @inject(CodeEditorStateManager)
64
+ protected readonly codeEditorStateManager: CodeEditorStateManager;
65
+
66
+ codeEditorInfoManager: CodeEditorInfoManager;
67
+
68
+ override view = CodeEditorRender;
69
+
70
+ protected classlist: string[] = [];
71
+
72
+ protected options: CodeEditorViewOptions;
73
+
74
+ protected modalChangeEmitter = new Emitter();
75
+
76
+ protected editorHostRef: React.RefObject<HTMLDivElement> | null | undefined;
77
+
78
+ get onModalChange() {
79
+ return this.modalChangeEmitter.event;
80
+ }
81
+
82
+ /**
83
+ * Get the editor wrapped by the widget.
84
+ */
85
+ editor: IEditor;
86
+
87
+ editorStatus: CodeEditorViewStatus = 'init';
88
+
89
+ protected editorStatusChangeEmitter = new Emitter<{
90
+ status: CodeEditorViewStatus;
91
+ prevState: CodeEditorViewStatus;
92
+ }>();
93
+ onEditorStatusChange = this.editorStatusChangeEmitter.event;
94
+
95
+ /**
96
+ * Construct a new code editor widget.
97
+ */
98
+ constructor(
99
+ @inject(ViewOption) options: CodeEditorViewOptions,
100
+ @inject(CodeEditorInfoManager) codeEditorInfoManager: CodeEditorInfoManager,
101
+ ) {
102
+ super();
103
+ this.options = options;
104
+ this.codeEditorInfoManager = codeEditorInfoManager;
105
+ }
106
+
107
+ protected getEditorHost() {
108
+ const editorHostId = this.options.editorHostId;
109
+ const editorHostRef = editorHostId
110
+ ? this.codeEditorInfoManager.getEditorHostRef(editorHostId)
111
+ : undefined;
112
+
113
+ return editorHostRef && editorHostRef.current ? editorHostRef : this.container;
114
+ }
115
+
116
+ override async onViewMount() {
117
+ const state = await this.codeEditorStateManager.getOrCreateEditorState({
118
+ uuid: this.options.uuid,
119
+ model: this.options.model,
120
+ });
121
+
122
+ const settings = this.codeEditorSettings.getUserEditorSettings();
123
+
124
+ this.editorHostRef = this.getEditorHost();
125
+
126
+ if (this.editorHostRef?.current && this.options.factory) {
127
+ this.editor = this.options.factory(
128
+ {
129
+ ...this.options,
130
+ host: this.editorHostRef.current,
131
+ model: this.options.model,
132
+ uuid: this.options.uuid,
133
+ config: { ...settings, ...this.options.config },
134
+ selectionStyle: this.options.selectionStyle,
135
+ tooltipProvider: this.options.tooltipProvider,
136
+ completionProvider: this.options.completionProvider,
137
+ },
138
+ state,
139
+ );
140
+
141
+ await this.editor.editorReady;
142
+
143
+ const { cursorPosition, selections } = state;
144
+
145
+ const prevState = this.editorStatus;
146
+ this.editorStatus = 'ready';
147
+ this.editorStatusChangeEmitter.fire({ status: 'ready', prevState: prevState });
148
+
149
+ if (cursorPosition) {
150
+ this.editor.setCursorPosition(cursorPosition);
151
+ }
152
+ if (selections) {
153
+ this.editor.setSelections(selections);
154
+ }
155
+
156
+ this.editor.onModalChange((val) => this.modalChangeEmitter.fire(val));
157
+ // this.editor.model.selections.changed(this._onSelectionsChanged);
158
+
159
+ if (this.options.autoFocus) {
160
+ this.editor.focus();
161
+ }
162
+
163
+ if (!this.editorHostRef.current) {
164
+ return;
165
+ }
166
+
167
+ this.editorHostRef.current.addEventListener('focus', this.onViewActive);
168
+ this.editorHostRef.current.addEventListener('dragenter', this._evtDragEnter);
169
+ this.editorHostRef.current.addEventListener('dragleave', this._evtDragLeave);
170
+ this.editorHostRef.current.addEventListener('dragover', this._evtDragOver);
171
+ this.editorHostRef.current.addEventListener('drop', this._evtDrop);
172
+
173
+ this.toDispose.push(
174
+ this.codeEditorSettings.onCodeEditorSettingsChange((e) => {
175
+ this.editor.setOption(e.key, e.value);
176
+ }),
177
+ );
178
+ }
179
+ }
180
+
181
+ removeChildNodes = (parent: any) => {
182
+ while (parent.firstChild) {
183
+ parent.removeChild(parent.firstChild);
184
+ }
185
+ };
186
+
187
+ override onViewUnmount = () => {
188
+ if (this.editor.hasFocus()) {
189
+ // 保存编辑器状态
190
+ const editorState = this.editor.getState();
191
+ this.codeEditorStateManager.updateEditorState(this.options.uuid, editorState);
192
+ // focus 到 host 避免进入命令模式
193
+ this.editorHostRef = this.getEditorHost();
194
+ this.editorHostRef?.current?.focus();
195
+ }
196
+ this.editor.dispose();
197
+
198
+ const prevState = this.editorStatus;
199
+ this.editorStatus = 'disposed';
200
+ this.editorStatusChangeEmitter.fire({ status: 'disposed', prevState: prevState });
201
+
202
+ const node = this.editorHostRef?.current;
203
+ if (node) {
204
+ node.removeEventListener('focus', this.onViewActive);
205
+ node.removeEventListener('dragenter', this._evtDragEnter);
206
+ node.removeEventListener('dragleave', this._evtDragLeave);
207
+ node.removeEventListener('dragover', this._evtDragOver);
208
+ node.removeEventListener('drop', this._evtDrop);
209
+
210
+ this.removeChildNodes(node);
211
+ }
212
+ };
213
+
214
+ override onViewResize() {
215
+ this.editor?.resizeToFit();
216
+ }
217
+
218
+ /**
219
+ * Get the model used by the widget.
220
+ */
221
+ get model(): IModel {
222
+ return this.editor.model;
223
+ }
224
+
225
+ /**
226
+ * Dispose of the resources held by the widget.
227
+ */
228
+ override dispose(): void {
229
+ if (this.isDisposed) {
230
+ return;
231
+ }
232
+ super.dispose();
233
+ this.editor.dispose();
234
+ }
235
+
236
+ protected onViewActive = (): void => {
237
+ this.editor.focus();
238
+ };
239
+
240
+ /**
241
+ * A message handler invoked on a `'resize'` message.
242
+ */
243
+ protected onResize(): void {
244
+ if (this.isVisible) {
245
+ this.editor.resizeToFit();
246
+ }
247
+ }
248
+
249
+ addClass(classname: string) {
250
+ this.classlist.push(classname);
251
+ }
252
+
253
+ removeClass(classname: string) {
254
+ const index = this.classlist.indexOf(classname);
255
+ if (index >= 0) {
256
+ this.classlist.splice(index, 1);
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Handle a change in model selections.
262
+ */
263
+ protected _onSelectionsChanged(): void {
264
+ const { start, end } = this.editor.getSelection();
265
+
266
+ if (start.column !== end.column || start.line !== end.line) {
267
+ // a selection was made
268
+ this.addClass(HAS_SELECTION_CLASS);
269
+ this.removeClass(HAS_IN_LEADING_WHITESPACE_CLASS);
270
+ } else {
271
+ // the cursor was placed
272
+ this.removeClass(HAS_SELECTION_CLASS);
273
+
274
+ if (
275
+ this.editor.getLine(end.line)!.slice(0, end.column).match(leadingWhitespaceRe)
276
+ ) {
277
+ this.addClass(HAS_IN_LEADING_WHITESPACE_CLASS);
278
+ } else {
279
+ this.removeClass(HAS_IN_LEADING_WHITESPACE_CLASS);
280
+ }
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Handle the `'lm-dragenter'` event for the widget.
286
+ */
287
+ protected _evtDragEnter = (event: DragEvent): void => {
288
+ if (this.editor.getOption('readOnly') === true) {
289
+ return;
290
+ }
291
+ const data = findTextData(event);
292
+ if (data === undefined) {
293
+ return;
294
+ }
295
+ event.preventDefault();
296
+ event.stopPropagation();
297
+ this.addClass('jp-mod-dropTarget');
298
+ };
299
+
300
+ /**
301
+ * Handle the `'lm-dragleave'` event for the widget.
302
+ */
303
+ protected _evtDragLeave = (event: DragEvent): void => {
304
+ this.removeClass(DROP_TARGET_CLASS);
305
+ if (this.editor.getOption('readOnly') === true) {
306
+ return;
307
+ }
308
+ const data = findTextData(event);
309
+ if (data === undefined) {
310
+ return;
311
+ }
312
+ event.preventDefault();
313
+ event.stopPropagation();
314
+ };
315
+
316
+ /**
317
+ * Handle the `'lm-dragover'` event for the widget.
318
+ */
319
+ protected _evtDragOver = (event: DragEvent): void => {
320
+ this.removeClass(DROP_TARGET_CLASS);
321
+ if (this.editor.getOption('readOnly') === true) {
322
+ return;
323
+ }
324
+ const data = findTextData(event);
325
+ if (data === undefined) {
326
+ return;
327
+ }
328
+ event.preventDefault();
329
+ event.stopPropagation();
330
+ event.dataTransfer!.dropEffect = 'copy';
331
+ this.addClass(DROP_TARGET_CLASS);
332
+ };
333
+
334
+ /**
335
+ * Handle the `'lm-drop'` event for the widget.
336
+ */
337
+ protected _evtDrop = (event: DragEvent): void => {
338
+ if (this.editor.getOption('readOnly') === true) {
339
+ return;
340
+ }
341
+ const data = findTextData(event);
342
+ if (data === undefined) {
343
+ return;
344
+ }
345
+ const coordinate = {
346
+ top: event.y,
347
+ bottom: event.y,
348
+ left: event.x,
349
+ right: event.x,
350
+ x: event.x,
351
+ y: event.y,
352
+ width: 0,
353
+ height: 0,
354
+ } as ICoordinate;
355
+ const position = this.editor.getPositionForCoordinate(coordinate);
356
+ if (position === null) {
357
+ return;
358
+ }
359
+ this.removeClass(DROP_TARGET_CLASS);
360
+ event.preventDefault();
361
+ event.stopPropagation();
362
+ // if (event.proposedAction === 'none') {
363
+ // event.dropAction = 'none';
364
+ // return;
365
+ // }
366
+ // const offset = this.editor.getOffsetAt(position);
367
+ // this.model.value.insert(offset, data);
368
+ };
369
+ }
370
+
371
+ /**
372
+ * The namespace for the `CodeEditorWrapper` statics.
373
+ */
374
+ /**
375
+ * The options used to initialize a code editor widget.
376
+ */
377
+ export interface CodeEditorViewOptions<Config extends IEditorConfig = IEditorConfig> {
378
+ /**
379
+ * A code editor factory.
380
+ *
381
+ * #### Notes
382
+ * The widget needs a factory and a model instead of a `CodeEditor.IEditor`
383
+ * object because it needs to provide its own node as the host.
384
+ */
385
+ factory?: CodeEditorFactory;
386
+
387
+ /**
388
+ * where to mount the editor
389
+ */
390
+ editorHostId?: string;
391
+
392
+ /**
393
+ * The model used to initialize the code editor.
394
+ */
395
+ model: IModel;
396
+
397
+ /**
398
+ * The desired uuid for the editor.
399
+ * editor share id with cell.
400
+ */
401
+ uuid: string;
402
+
403
+ /**
404
+ * The configuration options for the editor.
405
+ */
406
+ config?: Partial<Config>;
407
+
408
+ /**
409
+ * The default selection style for the editor.
410
+ */
411
+ selectionStyle?: IEditorSelectionStyle;
412
+
413
+ tooltipProvider?: TooltipProvider;
414
+ completionProvider?: CompletionProvider;
415
+
416
+ autoFocus?: boolean;
417
+
418
+ [key: string]: any;
419
+ }
420
+
421
+ /**
422
+ * Given a MimeData instance, extract the first text data, if any.
423
+ */
424
+ function findTextData(event: DragEvent): string | undefined {
425
+ const items = event.dataTransfer?.items;
426
+ if (!items) {
427
+ return;
428
+ }
429
+ const textTypeItem = Array.from(items).find((t) => t.type.indexOf('text') === 0);
430
+ if (textTypeItem === undefined) {
431
+ return undefined;
432
+ }
433
+ return event.dataTransfer.getData(textTypeItem.type);
434
+ }
@@ -0,0 +1,10 @@
1
+ import 'reflect-metadata';
2
+ import assert from 'assert';
3
+
4
+ import { CodeEditorView } from './index.js';
5
+
6
+ describe('libro-code-editor', () => {
7
+ it('#import', () => {
8
+ assert(CodeEditorView);
9
+ });
10
+ });