@xh/hoist 79.0.0-SNAPSHOT.1765828486265 → 79.0.0-SNAPSHOT.1765833120742
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/CHANGELOG.md +26 -0
- package/admin/tabs/cluster/instances/connpool/ConnPoolMonitorPanel.ts +1 -1
- package/admin/tabs/cluster/instances/services/DetailsPanel.ts +1 -1
- package/admin/tabs/cluster/objects/DetailPanel.ts +1 -1
- package/build/types/core/HoistBase.d.ts +1 -1
- package/build/types/desktop/cmp/input/CodeInput.d.ts +9 -21
- package/build/types/desktop/cmp/input/JsonInput.d.ts +15 -3
- package/build/types/desktop/cmp/input/impl/one-dark.d.ts +23 -0
- package/core/HoistBase.ts +1 -7
- package/desktop/cmp/filechooser/FileChooserModel.ts +1 -2
- package/desktop/cmp/input/CodeInput.scss +21 -15
- package/desktop/cmp/input/CodeInput.ts +263 -194
- package/desktop/cmp/input/JsonInput.ts +126 -22
- package/desktop/cmp/input/impl/one-dark.ts +163 -0
- package/package.json +13 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -4,6 +4,38 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import {autocompletion} from '@codemirror/autocomplete';
|
|
8
|
+
import {defaultKeymap, history, historyKeymap, indentWithTab} from '@codemirror/commands';
|
|
9
|
+
import {
|
|
10
|
+
defaultHighlightStyle,
|
|
11
|
+
foldGutter,
|
|
12
|
+
foldKeymap,
|
|
13
|
+
indentOnInput,
|
|
14
|
+
LanguageSupport,
|
|
15
|
+
syntaxHighlighting
|
|
16
|
+
} from '@codemirror/language';
|
|
17
|
+
import {linter, lintGutter} from '@codemirror/lint';
|
|
18
|
+
import {highlightSelectionMatches, search} from '@codemirror/search';
|
|
19
|
+
import {
|
|
20
|
+
Compartment,
|
|
21
|
+
EditorState,
|
|
22
|
+
Extension,
|
|
23
|
+
RangeSetBuilder,
|
|
24
|
+
StateEffect,
|
|
25
|
+
StateField
|
|
26
|
+
} from '@codemirror/state';
|
|
27
|
+
import {
|
|
28
|
+
Decoration,
|
|
29
|
+
DecorationSet,
|
|
30
|
+
EditorView,
|
|
31
|
+
highlightActiveLine,
|
|
32
|
+
highlightActiveLineGutter,
|
|
33
|
+
keymap,
|
|
34
|
+
lineNumbers,
|
|
35
|
+
ViewPlugin,
|
|
36
|
+
ViewUpdate
|
|
37
|
+
} from '@codemirror/view';
|
|
38
|
+
import {oneDark} from './impl/one-dark';
|
|
7
39
|
import {HoistInputModel, HoistInputProps, useHoistInputModel} from '@xh/hoist/cmp/input';
|
|
8
40
|
import {box, div, filler, fragment, frame, hbox, label, span, vbox} from '@xh/hoist/cmp/layout';
|
|
9
41
|
import {hoistCmp, HoistProps, LayoutProps, managed, PlainObject, XH} from '@xh/hoist/core';
|
|
@@ -13,32 +45,19 @@ import {textInput} from '@xh/hoist/desktop/cmp/input/TextInput';
|
|
|
13
45
|
import {modalSupport} from '@xh/hoist/desktop/cmp/modalsupport/ModalSupport';
|
|
14
46
|
import {ModalSupportModel} from '@xh/hoist/desktop/cmp/modalsupport/ModalSupportModel';
|
|
15
47
|
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
16
|
-
import '@xh/hoist/desktop/register';
|
|
17
48
|
import {Icon} from '@xh/hoist/icon';
|
|
18
|
-
import {textArea} from '@xh/hoist/kit/blueprint';
|
|
19
49
|
import {action, bindable, makeObservable, observable} from '@xh/hoist/mobx';
|
|
20
|
-
import {wait} from '@xh/hoist/promise';
|
|
21
50
|
import {withDefault} from '@xh/hoist/utils/js';
|
|
22
51
|
import {getLayoutProps} from '@xh/hoist/utils/react';
|
|
23
52
|
import classNames from 'classnames';
|
|
24
|
-
import
|
|
25
|
-
import 'codemirror/addon/fold/brace-fold.js';
|
|
26
|
-
import 'codemirror/addon/fold/foldcode.js';
|
|
27
|
-
import 'codemirror/addon/fold/foldgutter.css';
|
|
28
|
-
import 'codemirror/addon/fold/foldgutter.js';
|
|
29
|
-
import 'codemirror/addon/lint/lint.css';
|
|
30
|
-
import 'codemirror/addon/lint/lint.js';
|
|
31
|
-
import 'codemirror/addon/scroll/simplescrollbars.css';
|
|
32
|
-
import 'codemirror/addon/scroll/simplescrollbars.js';
|
|
33
|
-
import 'codemirror/addon/search/searchcursor.js';
|
|
34
|
-
import 'codemirror/addon/selection/mark-selection.js';
|
|
35
|
-
import 'codemirror/lib/codemirror.css';
|
|
36
|
-
import 'codemirror/theme/dracula.css';
|
|
37
|
-
import {compact, defaultsDeep, isEqual, isFunction} from 'lodash';
|
|
53
|
+
import {compact, isEmpty, isFunction, isObject} from 'lodash';
|
|
38
54
|
import {ReactElement} from 'react';
|
|
39
|
-
import {findDOMNode} from 'react-dom';
|
|
40
55
|
import './CodeInput.scss';
|
|
41
|
-
|
|
56
|
+
import {javascript} from '@codemirror/lang-javascript';
|
|
57
|
+
import {json} from '@codemirror/lang-json';
|
|
58
|
+
import {html} from '@codemirror/lang-html';
|
|
59
|
+
import {sql} from '@codemirror/lang-sql';
|
|
60
|
+
import {css} from '@codemirror/lang-css';
|
|
42
61
|
export interface CodeInputProps extends HoistProps, HoistInputProps, LayoutProps {
|
|
43
62
|
/** True to focus the control on render. */
|
|
44
63
|
autoFocus?: boolean;
|
|
@@ -46,12 +65,6 @@ export interface CodeInputProps extends HoistProps, HoistInputProps, LayoutProps
|
|
|
46
65
|
/** False to not commit on every change/keystroke, default true. */
|
|
47
66
|
commitOnChange?: boolean;
|
|
48
67
|
|
|
49
|
-
/**
|
|
50
|
-
* Configuration object with any properties supported by the CodeMirror API.
|
|
51
|
-
* @see {@link https://codemirror.net/doc/manual.html#api_configuration|CodeMirror Docs}
|
|
52
|
-
*/
|
|
53
|
-
editorProps?: PlainObject;
|
|
54
|
-
|
|
55
68
|
/**
|
|
56
69
|
* True to enable case-insensitive searching within the input. Default false, except in
|
|
57
70
|
* fullscreen mode, where search will be shown unless explicitly *disabled*. Note that
|
|
@@ -72,10 +85,10 @@ export interface CodeInputProps extends HoistProps, HoistInputProps, LayoutProps
|
|
|
72
85
|
|
|
73
86
|
/**
|
|
74
87
|
* A CodeMirror language mode - default none (plain-text). See the CodeMirror docs
|
|
75
|
-
* ({@link https://
|
|
76
|
-
*
|
|
88
|
+
* ({@link https://github.com/codemirror/language-data/blob/main/src/language-data.ts}) regarding available languages.
|
|
89
|
+
* String can be the alias or name
|
|
77
90
|
*/
|
|
78
|
-
|
|
91
|
+
language?: string;
|
|
79
92
|
|
|
80
93
|
/**
|
|
81
94
|
* True to prevent user modification of editor contents, while still allowing user to
|
|
@@ -101,6 +114,15 @@ export interface CodeInputProps extends HoistProps, HoistInputProps, LayoutProps
|
|
|
101
114
|
* action buttons show only when the input focused and float in the bottom-right corner.
|
|
102
115
|
*/
|
|
103
116
|
showToolbar?: boolean;
|
|
117
|
+
|
|
118
|
+
/** False (default) to highlight active line in input. */
|
|
119
|
+
highlightActiveLine?: boolean;
|
|
120
|
+
|
|
121
|
+
/** True (default) to add line numbers to gutter. */
|
|
122
|
+
lineNumbers?: boolean | PlainObject;
|
|
123
|
+
|
|
124
|
+
/** False (default) to add line numbers to gutter. */
|
|
125
|
+
lineWrapping?: boolean | PlainObject;
|
|
104
126
|
}
|
|
105
127
|
|
|
106
128
|
/**
|
|
@@ -131,17 +153,17 @@ class CodeInputModel extends HoistInputModel {
|
|
|
131
153
|
@managed
|
|
132
154
|
modalSupportModel: ModalSupportModel = new ModalSupportModel();
|
|
133
155
|
|
|
134
|
-
|
|
135
|
-
editor: any;
|
|
156
|
+
editor: EditorView;
|
|
136
157
|
|
|
137
158
|
// Support for internal search feature.
|
|
138
159
|
cursor = null;
|
|
139
160
|
@bindable query: string = '';
|
|
140
161
|
@observable currentMatchIdx: number = -1;
|
|
141
|
-
@observable.ref matches = [];
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
162
|
+
@observable.ref matches: {from: number; to: number}[] = [];
|
|
163
|
+
|
|
164
|
+
private updateMatchesEffect = StateEffect.define<void>();
|
|
165
|
+
private highlightField: StateField<DecorationSet>;
|
|
166
|
+
private themeCompartment = new Compartment();
|
|
145
167
|
|
|
146
168
|
get fullScreen(): boolean {
|
|
147
169
|
return this.modalSupportModel.isModal;
|
|
@@ -214,8 +236,7 @@ class CodeInputModel extends HoistInputModel {
|
|
|
214
236
|
}
|
|
215
237
|
|
|
216
238
|
override blur() {
|
|
217
|
-
this.editor?.
|
|
218
|
-
this.editor?.getInputField().blur();
|
|
239
|
+
this.editor?.contentDOM.blur();
|
|
219
240
|
}
|
|
220
241
|
|
|
221
242
|
override focus() {
|
|
@@ -223,12 +244,34 @@ class CodeInputModel extends HoistInputModel {
|
|
|
223
244
|
}
|
|
224
245
|
|
|
225
246
|
override select() {
|
|
226
|
-
this.editor
|
|
247
|
+
if (!this.editor) return;
|
|
248
|
+
this.editor.dispatch({selection: {anchor: 0, head: this.editor.state.doc.length}});
|
|
227
249
|
}
|
|
228
250
|
|
|
229
251
|
constructor() {
|
|
230
252
|
super();
|
|
231
253
|
makeObservable(this);
|
|
254
|
+
|
|
255
|
+
this.highlightField = StateField.define<DecorationSet>({
|
|
256
|
+
create: () => Decoration.none,
|
|
257
|
+
update: (deco, tr) => {
|
|
258
|
+
deco = deco.map(tr.changes);
|
|
259
|
+
if (tr.effects.some(e => e.is(this.updateMatchesEffect))) {
|
|
260
|
+
const builder = new RangeSetBuilder<Decoration>();
|
|
261
|
+
this.matches.forEach(match => {
|
|
262
|
+
builder.add(
|
|
263
|
+
match.from,
|
|
264
|
+
match.to,
|
|
265
|
+
Decoration.mark({class: 'xh-code-input--highlight'})
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
deco = builder.finish();
|
|
269
|
+
}
|
|
270
|
+
return deco;
|
|
271
|
+
},
|
|
272
|
+
provide: f => EditorView.decorations.from(f)
|
|
273
|
+
});
|
|
274
|
+
|
|
232
275
|
this.addReaction({
|
|
233
276
|
track: () => this.modalSupportModel.isModal,
|
|
234
277
|
run: () => this.focus(),
|
|
@@ -240,103 +283,61 @@ class CodeInputModel extends HoistInputModel {
|
|
|
240
283
|
this.addReaction({
|
|
241
284
|
track: () => XH.darkTheme,
|
|
242
285
|
run: () => {
|
|
243
|
-
|
|
244
|
-
|
|
286
|
+
if (!this.editor) return;
|
|
287
|
+
this.editor.dispatch({
|
|
288
|
+
effects: this.themeCompartment.reconfigure(this.getThemeExtension())
|
|
289
|
+
});
|
|
245
290
|
}
|
|
246
291
|
});
|
|
247
292
|
|
|
248
293
|
this.addReaction({
|
|
249
294
|
track: () => this.renderValue,
|
|
250
|
-
run:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
295
|
+
run: val => {
|
|
296
|
+
if (this.editor && this.editor.state.doc.toString() !== val) {
|
|
297
|
+
this.editor.dispatch({
|
|
298
|
+
changes: {from: 0, to: this.editor.state.doc.length, insert: val ?? ''}
|
|
299
|
+
});
|
|
255
300
|
}
|
|
256
301
|
}
|
|
257
302
|
});
|
|
258
303
|
|
|
259
304
|
this.addReaction({
|
|
260
305
|
track: () => this.componentProps.readonly || this.componentProps.disabled,
|
|
261
|
-
run:
|
|
262
|
-
this.editor
|
|
306
|
+
run: readOnly => {
|
|
307
|
+
if (this.editor)
|
|
308
|
+
this.editor.dispatch({
|
|
309
|
+
effects: StateEffect.appendConfig.of(EditorView.editable.of(!readOnly))
|
|
310
|
+
});
|
|
263
311
|
}
|
|
264
312
|
});
|
|
265
313
|
|
|
266
314
|
this.addReaction({
|
|
267
315
|
track: () => this.query,
|
|
268
316
|
run: query => {
|
|
269
|
-
if (query?.trim())
|
|
270
|
-
|
|
271
|
-
} else {
|
|
272
|
-
this.clearSearchResults();
|
|
273
|
-
}
|
|
317
|
+
if (query?.trim()) this.findAll();
|
|
318
|
+
else this.clearSearchResults();
|
|
274
319
|
},
|
|
275
320
|
debounce: 300
|
|
276
321
|
});
|
|
277
322
|
}
|
|
278
323
|
|
|
279
|
-
|
|
280
|
-
if (
|
|
281
|
-
|
|
282
|
-
this.preserveSearchResults();
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
createCodeEditor(textAreaComp) {
|
|
287
|
-
const editorSpec = defaultsDeep(this.componentProps.editorProps, this.createDefaults());
|
|
288
|
-
|
|
289
|
-
const taDom = findDOMNode(textAreaComp),
|
|
290
|
-
editor = codemirror.fromTextArea(taDom, editorSpec);
|
|
291
|
-
|
|
292
|
-
editor.on('change', this.handleEditorChange);
|
|
293
|
-
return editor;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
createDefaults() {
|
|
297
|
-
const {disabled, readonly, mode, linter, autoFocus} = this.componentProps;
|
|
298
|
-
let gutters = ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'];
|
|
299
|
-
if (linter) gutters.push('CodeMirror-lint-markers');
|
|
300
|
-
|
|
301
|
-
return {
|
|
302
|
-
mode,
|
|
303
|
-
theme: XH.darkTheme ? 'dracula' : 'default',
|
|
304
|
-
lineWrapping: false,
|
|
305
|
-
lineNumbers: true,
|
|
306
|
-
autoCloseBrackets: true,
|
|
307
|
-
extraKeys: {
|
|
308
|
-
'Cmd-P': this.onAutoFormat,
|
|
309
|
-
'Ctrl-P': this.onAutoFormat
|
|
310
|
-
},
|
|
311
|
-
foldGutter: true,
|
|
312
|
-
scrollbarStyle: 'simple',
|
|
313
|
-
readOnly: disabled || readonly,
|
|
314
|
-
gutters,
|
|
315
|
-
lint: linter ? {getAnnotations: linter} : false,
|
|
316
|
-
autoFocus
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
onChange = ev => {
|
|
321
|
-
this.noteValueChange(ev.target.value);
|
|
322
|
-
};
|
|
324
|
+
createCodeEditor = async (container: HTMLElement) => {
|
|
325
|
+
if (!container) return;
|
|
326
|
+
const extensions = await this.getExtensionsAsync();
|
|
323
327
|
|
|
324
|
-
|
|
325
|
-
this.
|
|
326
|
-
if (this.cursor) this.clearSearchResults();
|
|
328
|
+
const state = EditorState.create({doc: this.renderValue || '', extensions});
|
|
329
|
+
this.editor = new EditorView({state, parent: container});
|
|
327
330
|
};
|
|
328
331
|
|
|
329
|
-
onAutoFormat
|
|
330
|
-
if (!
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
editor.setValue(val);
|
|
335
|
-
};
|
|
332
|
+
onAutoFormat() {
|
|
333
|
+
if (!this.editor) return;
|
|
334
|
+
const val = this.tryPrettyPrint(this.editor.state.doc.toString());
|
|
335
|
+
this.editor.dispatch({changes: {from: 0, to: this.editor.state.doc.length, insert: val}});
|
|
336
|
+
}
|
|
336
337
|
|
|
337
|
-
tryPrettyPrint(str) {
|
|
338
|
+
tryPrettyPrint(str: string) {
|
|
338
339
|
try {
|
|
339
|
-
return this.componentProps.formatter(str);
|
|
340
|
+
return this.componentProps.formatter?.(str) ?? str;
|
|
340
341
|
} catch (e) {
|
|
341
342
|
return str;
|
|
342
343
|
}
|
|
@@ -344,106 +345,180 @@ class CodeInputModel extends HoistInputModel {
|
|
|
344
345
|
|
|
345
346
|
toggleFullScreen() {
|
|
346
347
|
this.modalSupportModel.toggleIsModal();
|
|
347
|
-
|
|
348
|
-
// 'Nudge' the mouse wheel to trigger CodeMirror to update scrollbar state
|
|
349
|
-
const scrollEvent = d => new window.WheelEvent('mousewheel', {deltaX: d, deltaY: d});
|
|
350
|
-
wait().then(() => {
|
|
351
|
-
this.editor.getScrollerElement().dispatchEvent(scrollEvent(2));
|
|
352
|
-
this.editor.getScrollerElement().dispatchEvent(scrollEvent(-2));
|
|
353
|
-
});
|
|
354
348
|
}
|
|
355
349
|
|
|
356
|
-
//------------------------
|
|
357
|
-
// Local Searching
|
|
358
|
-
//------------------------
|
|
359
350
|
@action
|
|
360
351
|
findAll() {
|
|
361
|
-
this.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
while (cursor.findNext()) {
|
|
370
|
-
const anchor = cursor.from(),
|
|
371
|
-
head = cursor.to();
|
|
372
|
-
newMatches.push({
|
|
373
|
-
anchor,
|
|
374
|
-
head,
|
|
375
|
-
textMarker: editor.markText(anchor, head, {className: 'xh-code-input--highlight'})
|
|
376
|
-
});
|
|
352
|
+
if (!this.editor || !this.query?.trim()) return;
|
|
353
|
+
|
|
354
|
+
let doc = this.editor.state.doc.toString(),
|
|
355
|
+
matches = [],
|
|
356
|
+
idx = doc.indexOf(this.query);
|
|
357
|
+
while (idx !== -1) {
|
|
358
|
+
matches.push({from: idx, to: idx + this.query.length});
|
|
359
|
+
idx = doc.indexOf(this.query, idx + 1);
|
|
377
360
|
}
|
|
378
|
-
|
|
379
|
-
this.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
361
|
+
this.matches = matches;
|
|
362
|
+
this.currentMatchIdx = matches.length ? 0 : -1;
|
|
363
|
+
this.updateMatchDecorations();
|
|
364
|
+
|
|
365
|
+
if (matches.length) {
|
|
366
|
+
const match = matches[0];
|
|
367
|
+
this.editor.dispatch({
|
|
368
|
+
selection: {anchor: match.from, head: match.to},
|
|
369
|
+
scrollIntoView: true
|
|
370
|
+
});
|
|
384
371
|
}
|
|
385
372
|
}
|
|
386
373
|
|
|
387
374
|
@action
|
|
388
375
|
findNext() {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
this.cursor = editor.getSearchCursor(query, 0, true);
|
|
397
|
-
this.findNext();
|
|
398
|
-
}
|
|
376
|
+
if (!this.editor || !this.matches.length) return;
|
|
377
|
+
this.currentMatchIdx = (this.currentMatchIdx + 1) % this.matches.length;
|
|
378
|
+
const match = this.matches[this.currentMatchIdx];
|
|
379
|
+
this.editor.dispatch({
|
|
380
|
+
selection: {anchor: match.from, head: match.to},
|
|
381
|
+
scrollIntoView: true
|
|
382
|
+
});
|
|
399
383
|
}
|
|
400
384
|
|
|
401
385
|
@action
|
|
402
386
|
findPrevious() {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
this.findPrevious();
|
|
412
|
-
}
|
|
387
|
+
if (!this.editor || !this.matches.length) return;
|
|
388
|
+
this.currentMatchIdx =
|
|
389
|
+
(this.currentMatchIdx - 1 + this.matches.length) % this.matches.length;
|
|
390
|
+
const match = this.matches[this.currentMatchIdx];
|
|
391
|
+
this.editor.dispatch({
|
|
392
|
+
selection: {anchor: match.from, head: match.to},
|
|
393
|
+
scrollIntoView: true
|
|
394
|
+
});
|
|
413
395
|
}
|
|
414
396
|
|
|
415
397
|
@action
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
to = cursor.to();
|
|
420
|
-
editor.scrollIntoView({from, to}, 50);
|
|
421
|
-
editor.setSelection(from, to);
|
|
422
|
-
this.currentMatchIdx = matches.findIndex(match => isEqual(match.anchor, from));
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
preserveSearchResults() {
|
|
426
|
-
const {matches, editor} = this;
|
|
427
|
-
matches.forEach(match => {
|
|
428
|
-
match.textMarker = editor.markText(match.anchor, match.head, {
|
|
429
|
-
className: 'xh-code-input--highlight'
|
|
430
|
-
});
|
|
431
|
-
});
|
|
398
|
+
updateMatchDecorations() {
|
|
399
|
+
if (!this.editor) return;
|
|
400
|
+
this.editor.dispatch({effects: this.updateMatchesEffect.of()});
|
|
432
401
|
}
|
|
433
402
|
|
|
434
403
|
@action
|
|
435
404
|
clearSearchResults() {
|
|
436
|
-
this.cursor = null;
|
|
437
|
-
this.currentMatchIdx = -1;
|
|
438
|
-
this.matches.forEach(match => match.textMarker.clear());
|
|
439
405
|
this.matches = [];
|
|
406
|
+
this.currentMatchIdx = -1;
|
|
407
|
+
this.updateMatchDecorations();
|
|
440
408
|
}
|
|
441
409
|
|
|
442
410
|
override destroy() {
|
|
443
|
-
|
|
444
|
-
if (this.editor) this.editor.toTextArea();
|
|
411
|
+
this.editor?.destroy();
|
|
445
412
|
super.destroy();
|
|
446
413
|
}
|
|
414
|
+
|
|
415
|
+
//------------------------
|
|
416
|
+
// Implementation
|
|
417
|
+
//------------------------
|
|
418
|
+
private async getExtensionsAsync(): Promise<Extension[]> {
|
|
419
|
+
const {
|
|
420
|
+
autoFocus,
|
|
421
|
+
language,
|
|
422
|
+
readonly,
|
|
423
|
+
highlightActiveLine: propsHighlightActiveLine,
|
|
424
|
+
linter: propsLinter,
|
|
425
|
+
lineNumbers: propsLineNumbers = true,
|
|
426
|
+
lineWrapping: propsLineWrapping = false
|
|
427
|
+
} = this.componentProps,
|
|
428
|
+
extensions = [
|
|
429
|
+
// Theme
|
|
430
|
+
this.themeCompartment.of(this.getThemeExtension()),
|
|
431
|
+
// Editor state
|
|
432
|
+
EditorView.editable.of(!readonly),
|
|
433
|
+
EditorView.updateListener.of((update: ViewUpdate) => {
|
|
434
|
+
if (update.docChanged) this.noteValueChange(update.state.doc.toString());
|
|
435
|
+
}),
|
|
436
|
+
// Search & custom highlight
|
|
437
|
+
search(),
|
|
438
|
+
syntaxHighlighting(defaultHighlightStyle),
|
|
439
|
+
highlightSelectionMatches(),
|
|
440
|
+
this.highlightField,
|
|
441
|
+
// Editor UI
|
|
442
|
+
foldGutter(),
|
|
443
|
+
lintGutter(),
|
|
444
|
+
indentOnInput(),
|
|
445
|
+
autocompletion(),
|
|
446
|
+
history(),
|
|
447
|
+
// Linter
|
|
448
|
+
propsLinter
|
|
449
|
+
? linter(async view => {
|
|
450
|
+
const text = view.state.doc.toString();
|
|
451
|
+
return await propsLinter(text);
|
|
452
|
+
})
|
|
453
|
+
: [],
|
|
454
|
+
// Key bindings
|
|
455
|
+
keymap.of([
|
|
456
|
+
...defaultKeymap,
|
|
457
|
+
...historyKeymap,
|
|
458
|
+
...foldKeymap,
|
|
459
|
+
indentWithTab,
|
|
460
|
+
{
|
|
461
|
+
key: 'Mod-p',
|
|
462
|
+
run: () => {
|
|
463
|
+
this.onAutoFormat();
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
])
|
|
468
|
+
];
|
|
469
|
+
|
|
470
|
+
if (propsLineWrapping) extensions.push(EditorView.lineWrapping);
|
|
471
|
+
if (propsLineNumbers) {
|
|
472
|
+
isObject(propsLineNumbers)
|
|
473
|
+
? extensions.push(lineNumbers(propsLineNumbers))
|
|
474
|
+
: extensions.push(lineNumbers());
|
|
475
|
+
}
|
|
476
|
+
if (propsHighlightActiveLine)
|
|
477
|
+
extensions.push(highlightActiveLine(), highlightActiveLineGutter());
|
|
478
|
+
if (autoFocus) extensions.push(this.autofocusExtension);
|
|
479
|
+
if (language) extensions.push(await this.getLanguageExtensionAsync(language));
|
|
480
|
+
|
|
481
|
+
return extensions.filter(it => !isEmpty(it));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private getThemeExtension() {
|
|
485
|
+
const lightTheme = EditorView.theme({}, {dark: false});
|
|
486
|
+
return XH.darkTheme ? oneDark : lightTheme;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private LANGUAGE_EXTENSIONS: Record<string, () => LanguageSupport> = {
|
|
490
|
+
js: javascript,
|
|
491
|
+
javascript: javascript,
|
|
492
|
+
html: html,
|
|
493
|
+
css: css,
|
|
494
|
+
json: json,
|
|
495
|
+
sql: sql
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
private async getLanguageExtensionAsync(lang: string): Promise<LanguageSupport> {
|
|
499
|
+
if (!lang) return null;
|
|
500
|
+
const extFactory = this.LANGUAGE_EXTENSIONS[lang.toLowerCase()];
|
|
501
|
+
if (!extFactory) {
|
|
502
|
+
console.warn(`Language not found: ${lang}`);
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
try {
|
|
506
|
+
// DEBUG
|
|
507
|
+
console.log(extFactory());
|
|
508
|
+
return extFactory(); // returns a LanguageSupport instance
|
|
509
|
+
} catch (err) {
|
|
510
|
+
console.error(`Failed to load language: ${lang}`, err);
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private autofocusExtension = ViewPlugin.fromClass(
|
|
516
|
+
class {
|
|
517
|
+
constructor(view: EditorView) {
|
|
518
|
+
queueMicrotask(() => view.focus());
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
);
|
|
447
522
|
}
|
|
448
523
|
|
|
449
524
|
const cmp = hoistCmp.factory<CodeInputModel>(({model, className, ...props}, ref) => {
|
|
@@ -471,11 +546,7 @@ const inputCmp = hoistCmp.factory<CodeInputModel>(({model, ...props}, ref) =>
|
|
|
471
546
|
items: [
|
|
472
547
|
div({
|
|
473
548
|
className: 'xh-code-input__inner-wrapper',
|
|
474
|
-
|
|
475
|
-
value: model.renderValue || '',
|
|
476
|
-
inputRef: model.manageCodeEditor,
|
|
477
|
-
onChange: model.onChange
|
|
478
|
-
})
|
|
549
|
+
ref: model.createCodeEditor
|
|
479
550
|
}),
|
|
480
551
|
model.showToolbar ? toolbarCmp() : actionButtonsCmp()
|
|
481
552
|
],
|
|
@@ -496,7 +567,9 @@ const toolbarCmp = hoistCmp.factory<CodeInputModel>(({model}) => {
|
|
|
496
567
|
});
|
|
497
568
|
|
|
498
569
|
const searchInputCmp = hoistCmp.factory<CodeInputModel>(({model}) => {
|
|
499
|
-
const {query,
|
|
570
|
+
const {query, currentMatchIdx, matches, fullScreen} = model,
|
|
571
|
+
matchCount = matches.length;
|
|
572
|
+
|
|
500
573
|
return fragment(
|
|
501
574
|
// Frame wrapper added due to issues with textInput not supporting all layout props as it should.
|
|
502
575
|
frame({
|
|
@@ -505,20 +578,16 @@ const searchInputCmp = hoistCmp.factory<CodeInputModel>(({model}) => {
|
|
|
505
578
|
item: textInput({
|
|
506
579
|
width: null,
|
|
507
580
|
flex: 1,
|
|
508
|
-
model
|
|
581
|
+
model,
|
|
509
582
|
bind: 'query',
|
|
510
583
|
leftIcon: Icon.search(),
|
|
511
584
|
enableClear: true,
|
|
512
585
|
commitOnChange: true,
|
|
513
586
|
onKeyDown: e => {
|
|
514
587
|
if (e.key !== 'Enter') return;
|
|
515
|
-
if (!
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
model.findPrevious();
|
|
519
|
-
} else {
|
|
520
|
-
model.findNext();
|
|
521
|
-
}
|
|
588
|
+
if (!matches.length) model.findAll();
|
|
589
|
+
else if (e.shiftKey) model.findPrevious();
|
|
590
|
+
else model.findNext();
|
|
522
591
|
}
|
|
523
592
|
})
|
|
524
593
|
}),
|