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