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