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