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