@xh/hoist 79.0.0-SNAPSHOT.1765824964847 → 79.0.0-SNAPSHOT.1765828263630
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 -193
- package/desktop/cmp/input/JsonInput.ts +126 -22
- package/desktop/cmp/input/impl/one-dark.ts +163 -0
- package/package.json +14 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -4,6 +4,46 @@
|
|
|
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';
|
|
7
47
|
import {HoistInputModel, HoistInputProps, useHoistInputModel} from '@xh/hoist/cmp/input';
|
|
8
48
|
import {box, div, filler, fragment, frame, hbox, label, span, vbox} from '@xh/hoist/cmp/layout';
|
|
9
49
|
import {hoistCmp, HoistProps, LayoutProps, managed, PlainObject, XH} from '@xh/hoist/core';
|
|
@@ -13,31 +53,25 @@ import {textInput} from '@xh/hoist/desktop/cmp/input/TextInput';
|
|
|
13
53
|
import {modalSupport} from '@xh/hoist/desktop/cmp/modalsupport/ModalSupport';
|
|
14
54
|
import {ModalSupportModel} from '@xh/hoist/desktop/cmp/modalsupport/ModalSupportModel';
|
|
15
55
|
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
16
|
-
import '@xh/hoist/desktop/register';
|
|
17
56
|
import {Icon} from '@xh/hoist/icon';
|
|
18
|
-
import {textArea} from '@xh/hoist/kit/blueprint';
|
|
19
57
|
import {action, bindable, makeObservable, observable} from '@xh/hoist/mobx';
|
|
20
|
-
import {wait} from '@xh/hoist/promise';
|
|
21
58
|
import {withDefault} from '@xh/hoist/utils/js';
|
|
22
59
|
import {getLayoutProps} from '@xh/hoist/utils/react';
|
|
23
60
|
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';
|
|
61
|
+
import {compact, isEmpty, isFunction, isObject} from 'lodash';
|
|
38
62
|
import {ReactElement} from 'react';
|
|
39
|
-
import {findDOMNode} from 'react-dom';
|
|
40
63
|
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
|
+
};
|
|
41
75
|
|
|
42
76
|
export interface CodeInputProps extends HoistProps, HoistInputProps, LayoutProps {
|
|
43
77
|
/** True to focus the control on render. */
|
|
@@ -46,12 +80,6 @@ export interface CodeInputProps extends HoistProps, HoistInputProps, LayoutProps
|
|
|
46
80
|
/** False to not commit on every change/keystroke, default true. */
|
|
47
81
|
commitOnChange?: boolean;
|
|
48
82
|
|
|
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
83
|
/**
|
|
56
84
|
* True to enable case-insensitive searching within the input. Default false, except in
|
|
57
85
|
* fullscreen mode, where search will be shown unless explicitly *disabled*. Note that
|
|
@@ -72,10 +100,10 @@ export interface CodeInputProps extends HoistProps, HoistInputProps, LayoutProps
|
|
|
72
100
|
|
|
73
101
|
/**
|
|
74
102
|
* A CodeMirror language mode - default none (plain-text). See the CodeMirror docs
|
|
75
|
-
* ({@link https://
|
|
76
|
-
*
|
|
103
|
+
* ({@link https://github.com/codemirror/language-data/blob/main/src/language-data.ts}) regarding available languages.
|
|
104
|
+
* String can be the alias or name
|
|
77
105
|
*/
|
|
78
|
-
|
|
106
|
+
language?: string;
|
|
79
107
|
|
|
80
108
|
/**
|
|
81
109
|
* True to prevent user modification of editor contents, while still allowing user to
|
|
@@ -101,6 +129,15 @@ export interface CodeInputProps extends HoistProps, HoistInputProps, LayoutProps
|
|
|
101
129
|
* action buttons show only when the input focused and float in the bottom-right corner.
|
|
102
130
|
*/
|
|
103
131
|
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;
|
|
104
141
|
}
|
|
105
142
|
|
|
106
143
|
/**
|
|
@@ -131,17 +168,17 @@ class CodeInputModel extends HoistInputModel {
|
|
|
131
168
|
@managed
|
|
132
169
|
modalSupportModel: ModalSupportModel = new ModalSupportModel();
|
|
133
170
|
|
|
134
|
-
|
|
135
|
-
editor: any;
|
|
171
|
+
editor: EditorView;
|
|
136
172
|
|
|
137
173
|
// Support for internal search feature.
|
|
138
174
|
cursor = null;
|
|
139
175
|
@bindable query: string = '';
|
|
140
176
|
@observable currentMatchIdx: number = -1;
|
|
141
|
-
@observable.ref matches = [];
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
177
|
+
@observable.ref matches: {from: number; to: number}[] = [];
|
|
178
|
+
|
|
179
|
+
private updateMatchesEffect = StateEffect.define<void>();
|
|
180
|
+
private highlightField: StateField<DecorationSet>;
|
|
181
|
+
private themeCompartment = new Compartment();
|
|
145
182
|
|
|
146
183
|
get fullScreen(): boolean {
|
|
147
184
|
return this.modalSupportModel.isModal;
|
|
@@ -214,8 +251,7 @@ class CodeInputModel extends HoistInputModel {
|
|
|
214
251
|
}
|
|
215
252
|
|
|
216
253
|
override blur() {
|
|
217
|
-
this.editor?.
|
|
218
|
-
this.editor?.getInputField().blur();
|
|
254
|
+
this.editor?.contentDOM.blur();
|
|
219
255
|
}
|
|
220
256
|
|
|
221
257
|
override focus() {
|
|
@@ -223,12 +259,34 @@ class CodeInputModel extends HoistInputModel {
|
|
|
223
259
|
}
|
|
224
260
|
|
|
225
261
|
override select() {
|
|
226
|
-
this.editor
|
|
262
|
+
if (!this.editor) return;
|
|
263
|
+
this.editor.dispatch({selection: {anchor: 0, head: this.editor.state.doc.length}});
|
|
227
264
|
}
|
|
228
265
|
|
|
229
266
|
constructor() {
|
|
230
267
|
super();
|
|
231
268
|
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
|
+
|
|
232
290
|
this.addReaction({
|
|
233
291
|
track: () => this.modalSupportModel.isModal,
|
|
234
292
|
run: () => this.focus(),
|
|
@@ -240,103 +298,61 @@ class CodeInputModel extends HoistInputModel {
|
|
|
240
298
|
this.addReaction({
|
|
241
299
|
track: () => XH.darkTheme,
|
|
242
300
|
run: () => {
|
|
243
|
-
|
|
244
|
-
|
|
301
|
+
if (!this.editor) return;
|
|
302
|
+
this.editor.dispatch({
|
|
303
|
+
effects: this.themeCompartment.reconfigure(this.getThemeExtension())
|
|
304
|
+
});
|
|
245
305
|
}
|
|
246
306
|
});
|
|
247
307
|
|
|
248
308
|
this.addReaction({
|
|
249
309
|
track: () => this.renderValue,
|
|
250
|
-
run:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
310
|
+
run: val => {
|
|
311
|
+
if (this.editor && this.editor.state.doc.toString() !== val) {
|
|
312
|
+
this.editor.dispatch({
|
|
313
|
+
changes: {from: 0, to: this.editor.state.doc.length, insert: val ?? ''}
|
|
314
|
+
});
|
|
255
315
|
}
|
|
256
316
|
}
|
|
257
317
|
});
|
|
258
318
|
|
|
259
319
|
this.addReaction({
|
|
260
320
|
track: () => this.componentProps.readonly || this.componentProps.disabled,
|
|
261
|
-
run:
|
|
262
|
-
this.editor
|
|
321
|
+
run: readOnly => {
|
|
322
|
+
if (this.editor)
|
|
323
|
+
this.editor.dispatch({
|
|
324
|
+
effects: StateEffect.appendConfig.of(EditorView.editable.of(!readOnly))
|
|
325
|
+
});
|
|
263
326
|
}
|
|
264
327
|
});
|
|
265
328
|
|
|
266
329
|
this.addReaction({
|
|
267
330
|
track: () => this.query,
|
|
268
331
|
run: query => {
|
|
269
|
-
if (query?.trim())
|
|
270
|
-
|
|
271
|
-
} else {
|
|
272
|
-
this.clearSearchResults();
|
|
273
|
-
}
|
|
332
|
+
if (query?.trim()) this.findAll();
|
|
333
|
+
else this.clearSearchResults();
|
|
274
334
|
},
|
|
275
335
|
debounce: 300
|
|
276
336
|
});
|
|
277
337
|
}
|
|
278
338
|
|
|
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
|
-
};
|
|
339
|
+
createCodeEditor = async (container: HTMLElement) => {
|
|
340
|
+
if (!container) return;
|
|
341
|
+
const extensions = await this.getExtensionsAsync();
|
|
323
342
|
|
|
324
|
-
|
|
325
|
-
this.
|
|
326
|
-
if (this.cursor) this.clearSearchResults();
|
|
343
|
+
const state = EditorState.create({doc: this.renderValue || '', extensions});
|
|
344
|
+
this.editor = new EditorView({state, parent: container});
|
|
327
345
|
};
|
|
328
346
|
|
|
329
|
-
onAutoFormat
|
|
330
|
-
if (!
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
editor.setValue(val);
|
|
335
|
-
};
|
|
347
|
+
onAutoFormat() {
|
|
348
|
+
if (!this.editor) return;
|
|
349
|
+
const val = this.tryPrettyPrint(this.editor.state.doc.toString());
|
|
350
|
+
this.editor.dispatch({changes: {from: 0, to: this.editor.state.doc.length, insert: val}});
|
|
351
|
+
}
|
|
336
352
|
|
|
337
|
-
tryPrettyPrint(str) {
|
|
353
|
+
tryPrettyPrint(str: string) {
|
|
338
354
|
try {
|
|
339
|
-
return this.componentProps.formatter(str);
|
|
355
|
+
return this.componentProps.formatter?.(str) ?? str;
|
|
340
356
|
} catch (e) {
|
|
341
357
|
return str;
|
|
342
358
|
}
|
|
@@ -344,106 +360,169 @@ class CodeInputModel extends HoistInputModel {
|
|
|
344
360
|
|
|
345
361
|
toggleFullScreen() {
|
|
346
362
|
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
363
|
}
|
|
355
364
|
|
|
356
|
-
//------------------------
|
|
357
|
-
// Local Searching
|
|
358
|
-
//------------------------
|
|
359
365
|
@action
|
|
360
366
|
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
|
-
});
|
|
367
|
+
if (!this.editor || !this.query?.trim()) return;
|
|
368
|
+
|
|
369
|
+
let doc = this.editor.state.doc.toString(),
|
|
370
|
+
matches = [],
|
|
371
|
+
idx = doc.indexOf(this.query);
|
|
372
|
+
while (idx !== -1) {
|
|
373
|
+
matches.push({from: idx, to: idx + this.query.length});
|
|
374
|
+
idx = doc.indexOf(this.query, idx + 1);
|
|
377
375
|
}
|
|
378
|
-
|
|
379
|
-
this.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
376
|
+
this.matches = matches;
|
|
377
|
+
this.currentMatchIdx = matches.length ? 0 : -1;
|
|
378
|
+
this.updateMatchDecorations();
|
|
379
|
+
|
|
380
|
+
if (matches.length) {
|
|
381
|
+
const match = matches[0];
|
|
382
|
+
this.editor.dispatch({
|
|
383
|
+
selection: {anchor: match.from, head: match.to},
|
|
384
|
+
scrollIntoView: true
|
|
385
|
+
});
|
|
384
386
|
}
|
|
385
387
|
}
|
|
386
388
|
|
|
387
389
|
@action
|
|
388
390
|
findNext() {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
this.cursor = editor.getSearchCursor(query, 0, true);
|
|
397
|
-
this.findNext();
|
|
398
|
-
}
|
|
391
|
+
if (!this.editor || !this.matches.length) return;
|
|
392
|
+
this.currentMatchIdx = (this.currentMatchIdx + 1) % this.matches.length;
|
|
393
|
+
const match = this.matches[this.currentMatchIdx];
|
|
394
|
+
this.editor.dispatch({
|
|
395
|
+
selection: {anchor: match.from, head: match.to},
|
|
396
|
+
scrollIntoView: true
|
|
397
|
+
});
|
|
399
398
|
}
|
|
400
399
|
|
|
401
400
|
@action
|
|
402
401
|
findPrevious() {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
this.findPrevious();
|
|
412
|
-
}
|
|
402
|
+
if (!this.editor || !this.matches.length) return;
|
|
403
|
+
this.currentMatchIdx =
|
|
404
|
+
(this.currentMatchIdx - 1 + this.matches.length) % this.matches.length;
|
|
405
|
+
const match = this.matches[this.currentMatchIdx];
|
|
406
|
+
this.editor.dispatch({
|
|
407
|
+
selection: {anchor: match.from, head: match.to},
|
|
408
|
+
scrollIntoView: true
|
|
409
|
+
});
|
|
413
410
|
}
|
|
414
411
|
|
|
415
412
|
@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
|
-
});
|
|
413
|
+
updateMatchDecorations() {
|
|
414
|
+
if (!this.editor) return;
|
|
415
|
+
this.editor.dispatch({effects: this.updateMatchesEffect.of()});
|
|
432
416
|
}
|
|
433
417
|
|
|
434
418
|
@action
|
|
435
419
|
clearSearchResults() {
|
|
436
|
-
this.cursor = null;
|
|
437
|
-
this.currentMatchIdx = -1;
|
|
438
|
-
this.matches.forEach(match => match.textMarker.clear());
|
|
439
420
|
this.matches = [];
|
|
421
|
+
this.currentMatchIdx = -1;
|
|
422
|
+
this.updateMatchDecorations();
|
|
440
423
|
}
|
|
441
424
|
|
|
442
425
|
override destroy() {
|
|
443
|
-
|
|
444
|
-
if (this.editor) this.editor.toTextArea();
|
|
426
|
+
this.editor?.destroy();
|
|
445
427
|
super.destroy();
|
|
446
428
|
}
|
|
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
|
+
);
|
|
447
526
|
}
|
|
448
527
|
|
|
449
528
|
const cmp = hoistCmp.factory<CodeInputModel>(({model, className, ...props}, ref) => {
|
|
@@ -471,11 +550,7 @@ const inputCmp = hoistCmp.factory<CodeInputModel>(({model, ...props}, ref) =>
|
|
|
471
550
|
items: [
|
|
472
551
|
div({
|
|
473
552
|
className: 'xh-code-input__inner-wrapper',
|
|
474
|
-
|
|
475
|
-
value: model.renderValue || '',
|
|
476
|
-
inputRef: model.manageCodeEditor,
|
|
477
|
-
onChange: model.onChange
|
|
478
|
-
})
|
|
553
|
+
ref: model.createCodeEditor
|
|
479
554
|
}),
|
|
480
555
|
model.showToolbar ? toolbarCmp() : actionButtonsCmp()
|
|
481
556
|
],
|
|
@@ -496,7 +571,9 @@ const toolbarCmp = hoistCmp.factory<CodeInputModel>(({model}) => {
|
|
|
496
571
|
});
|
|
497
572
|
|
|
498
573
|
const searchInputCmp = hoistCmp.factory<CodeInputModel>(({model}) => {
|
|
499
|
-
const {query,
|
|
574
|
+
const {query, currentMatchIdx, matches, fullScreen} = model,
|
|
575
|
+
matchCount = matches.length;
|
|
576
|
+
|
|
500
577
|
return fragment(
|
|
501
578
|
// Frame wrapper added due to issues with textInput not supporting all layout props as it should.
|
|
502
579
|
frame({
|
|
@@ -505,20 +582,16 @@ const searchInputCmp = hoistCmp.factory<CodeInputModel>(({model}) => {
|
|
|
505
582
|
item: textInput({
|
|
506
583
|
width: null,
|
|
507
584
|
flex: 1,
|
|
508
|
-
model
|
|
585
|
+
model,
|
|
509
586
|
bind: 'query',
|
|
510
587
|
leftIcon: Icon.search(),
|
|
511
588
|
enableClear: true,
|
|
512
589
|
commitOnChange: true,
|
|
513
590
|
onKeyDown: e => {
|
|
514
591
|
if (e.key !== 'Enter') return;
|
|
515
|
-
if (!
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
model.findPrevious();
|
|
519
|
-
} else {
|
|
520
|
-
model.findNext();
|
|
521
|
-
}
|
|
592
|
+
if (!matches.length) model.findAll();
|
|
593
|
+
else if (e.shiftKey) model.findPrevious();
|
|
594
|
+
else model.findNext();
|
|
522
595
|
}
|
|
523
596
|
})
|
|
524
597
|
}),
|