Skip to content

Commit 07655cd

Browse files
committed
refactor: add more methods for code-editor instance
1 parent 6a6586a commit 07655cd

File tree

1 file changed

+142
-99
lines changed

1 file changed

+142
-99
lines changed

projects/code-editor/code-editor.ts

Lines changed: 142 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,27 @@ const External = Annotation.define<boolean>();
5353
})
5454
export class CodeEditor implements OnInit, OnDestroy, ControlValueAccessor {
5555
/**
56-
* The document or shadow [root](https://codemirror.net/docs/ref/#view.EditorView.root)
57-
* that the view lives in.
56+
* EditorView's [root](https://codemirror.net/docs/ref/#view.EditorView.root).
5857
*/
5958
@Input() root?: Document | ShadowRoot;
6059

6160
/** Whether focus on the editor after init. */
6261
@Input({ transform: booleanAttribute }) autoFocus = false;
6362

63+
/** Whether the editor is disabled. */
64+
@Input({ transform: booleanAttribute }) disabled = false;
65+
66+
/** Whether the editor is readonly. */
67+
@Input({ transform: booleanAttribute })
68+
get readonly() {
69+
return this._readonly;
70+
}
71+
set readonly(value: boolean) {
72+
this._readonly = value;
73+
this.setReadonly(value);
74+
}
75+
private _readonly = false;
76+
6477
/** Editor's value. */
6578
@Input()
6679
get value() {
@@ -79,9 +92,7 @@ export class CodeEditor implements OnInit, OnDestroy, ControlValueAccessor {
7992
}
8093
set theme(value: Theme) {
8194
this._theme = value;
82-
this._dispatchEffects(
83-
this._themeConf.reconfigure(value === 'light' ? [] : value === 'dark' ? oneDark : value)
84-
);
95+
this.setTheme(value);
8596
}
8697
private _theme: Theme = 'light';
8798

@@ -92,34 +103,18 @@ export class CodeEditor implements OnInit, OnDestroy, ControlValueAccessor {
92103
}
93104
set placeholder(value: string) {
94105
this._placeholder = value;
95-
this._dispatchEffects(this._placeholderConf.reconfigure(value ? placeholder(value) : []));
106+
this.setPlaceholder(value);
96107
}
97108
private _placeholder = '';
98109

99-
/** Whether the editor is disabled. */
100-
@Input({ transform: booleanAttribute }) disabled = false;
101-
102-
/** Whether the editor is readonly. */
103-
@Input({ transform: booleanAttribute })
104-
get readonly() {
105-
return this._readonly;
106-
}
107-
set readonly(value: boolean) {
108-
this._readonly = value;
109-
this._dispatchEffects(this._readonlyConf.reconfigure(EditorState.readOnly.of(value)));
110-
}
111-
private _readonly = false;
112-
113-
/** A binding that binds Tab to indentMore and Shift-Tab to indentLess. */
110+
/** Whether indent with Tab key. */
114111
@Input({ transform: booleanAttribute })
115112
get indentWithTab() {
116113
return this._indentWithTab;
117114
}
118115
set indentWithTab(value: boolean) {
119116
this._indentWithTab = value;
120-
this._dispatchEffects(
121-
this._indentWithTabConf.reconfigure(value ? keymap.of([indentWithTab]) : [])
122-
);
117+
this.setIndentWithTab(value);
123118
}
124119
private _indentWithTab = false;
125120

@@ -130,18 +125,18 @@ export class CodeEditor implements OnInit, OnDestroy, ControlValueAccessor {
130125
}
131126
set indentUnit(value: string) {
132127
this._indentUnit = value;
133-
this._dispatchEffects(this._indentUnitConf.reconfigure(value ? indentUnit.of(value) : []));
128+
this.setIndentUnit(value);
134129
}
135130
private _indentUnit = '';
136131

137-
/** Whether this editor wraps lines. */
132+
/** Whether the editor wraps lines. */
138133
@Input({ transform: booleanAttribute })
139134
get lineWrapping() {
140135
return this._lineWrapping;
141136
}
142137
set lineWrapping(value: boolean) {
143138
this._lineWrapping = value;
144-
this._dispatchEffects(this._lineWrappingConf.reconfigure(value ? EditorView.lineWrapping : []));
139+
this.setLineWrapping(value);
145140
}
146141
private _lineWrapping = false;
147142

@@ -152,15 +147,13 @@ export class CodeEditor implements OnInit, OnDestroy, ControlValueAccessor {
152147
}
153148
set highlightWhitespace(value: boolean) {
154149
this._highlightWhitespace = value;
155-
this._dispatchEffects(
156-
this._highlightWhitespaceConf.reconfigure(value ? highlightWhitespace() : [])
157-
);
150+
this.setHighlightWhitespace(value);
158151
}
159152
private _highlightWhitespace = false;
160153

161154
/**
162155
* An array of language descriptions for known
163-
* [language-data packages](https://github.com/codemirror/language-data/blob/main/src/language-data.ts).
156+
* [language-data](https://github.com/codemirror/language-data/blob/main/src/language-data.ts).
164157
*/
165158
@Input() languages: LanguageDescription[] = [];
166159

@@ -186,20 +179,21 @@ export class CodeEditor implements OnInit, OnDestroy, ControlValueAccessor {
186179
}
187180
set setup(value: Setup) {
188181
this._setup = value;
189-
this.reconfigure();
182+
this.setExtensions(this._getAllExtensions());
190183
}
191184
private _setup: Setup = 'basic';
192185

193186
/**
194-
* EditorState's [extensions](https://codemirror.net/docs/ref/#state.EditorStateConfig.extensions).
187+
* It will be appended to the root
188+
* [extensions](https://codemirror.net/docs/ref/#state.EditorStateConfig.extensions).
195189
*/
196190
@Input()
197191
get extensions() {
198192
return this._extensions;
199193
}
200194
set extensions(value: Extension[]) {
201195
this._extensions = value;
202-
this.reconfigure();
196+
this.setExtensions(this._getAllExtensions());
203197
}
204198
private _extensions: Extension[] = [];
205199

@@ -212,13 +206,16 @@ export class CodeEditor implements OnInit, OnDestroy, ControlValueAccessor {
212206
/** Event emitted when the editor has lost focus. */
213207
@Output() blur = new EventEmitter<void>();
214208

215-
view?: EditorView;
216-
217209
private _onChange: (value: string) => void = () => {};
218210
private _onTouched: () => void = () => {};
219211

220212
constructor(private _elementRef: ElementRef<Element>) {}
221213

214+
/**
215+
* The instance of [EditorView](https://codemirror.net/docs/ref/#view.EditorView).
216+
*/
217+
view?: EditorView;
218+
222219
private _updateListener = EditorView.updateListener.of(vu => {
223220
if (vu.docChanged && !vu.transactions.some(tr => tr.annotation(External))) {
224221
const value = vu.state.doc.toString();
@@ -229,24 +226,24 @@ export class CodeEditor implements OnInit, OnDestroy, ControlValueAccessor {
229226

230227
// Extension compartments can be used to make a configuration dynamic.
231228
// https://codemirror.net/docs/ref/#state.Compartment
229+
private _editableConf = new Compartment();
230+
private _readonlyConf = new Compartment();
232231
private _themeConf = new Compartment();
233232
private _placeholderConf = new Compartment();
234-
private _disabledConf = new Compartment();
235-
private _readonlyConf = new Compartment();
236233
private _indentWithTabConf = new Compartment();
237234
private _indentUnitConf = new Compartment();
238235
private _lineWrappingConf = new Compartment();
239236
private _highlightWhitespaceConf = new Compartment();
240237
private _languageConf = new Compartment();
241238

242-
private _getExtensions(): Extension[] {
239+
private _getAllExtensions() {
243240
return [
244241
this._updateListener,
245242

243+
this._editableConf.of([]),
244+
this._readonlyConf.of([]),
246245
this._themeConf.of([]),
247246
this._placeholderConf.of([]),
248-
this._disabledConf.of([]),
249-
this._readonlyConf.of([]),
250247
this._indentWithTabConf.of([]),
251248
this._indentUnitConf.of([]),
252249
this._lineWrappingConf.of([]),
@@ -259,57 +256,11 @@ export class CodeEditor implements OnInit, OnDestroy, ControlValueAccessor {
259256
];
260257
}
261258

262-
private _dispatchEffects(effects: StateEffect<any> | readonly StateEffect<any>[]) {
263-
return this.view?.dispatch({ effects });
264-
}
265-
266-
reconfigure() {
267-
this._dispatchEffects(StateEffect.reconfigure.of(this._getExtensions()));
268-
}
269-
270-
setValue(value: string) {
271-
this.view?.dispatch({
272-
changes: { from: 0, to: this.view.state.doc.length, insert: value },
273-
});
274-
}
275-
276-
/** Sets language dynamically. */
277-
setLanguage(lang: string, onInit?: boolean) {
278-
if (!lang) {
279-
return;
280-
}
281-
if (this.languages.length === 0) {
282-
onInit && console.error('No supported languages. Please set the languages prop at first.');
283-
return;
284-
}
285-
const langDesc = this.findLanguage(lang);
286-
langDesc?.load().then(lang => {
287-
this._dispatchEffects(this._languageConf.reconfigure([lang]));
288-
});
289-
}
290-
291-
/** Find the language's extension by its name. Case insensitive. */
292-
findLanguage(name: string) {
293-
for (const lang of this.languages) {
294-
for (const alias of [lang.name, ...lang.alias]) {
295-
if (name.toLowerCase() === alias.toLowerCase()) {
296-
return lang;
297-
}
298-
}
299-
}
300-
console.error('Language not found:', name);
301-
console.info('Supported language names:', this.languages.map(lang => lang.name).join(', '));
302-
return null;
303-
}
304-
305259
ngOnInit(): void {
306260
this.view = new EditorView({
307261
root: this.root,
308262
parent: this._elementRef.nativeElement,
309-
state: EditorState.create({
310-
doc: this.value,
311-
extensions: this._getExtensions(),
312-
}),
263+
state: EditorState.create({ doc: this.value, extensions: this._getAllExtensions() }),
313264
});
314265

315266
if (this.autoFocus) {
@@ -326,16 +277,15 @@ export class CodeEditor implements OnInit, OnDestroy, ControlValueAccessor {
326277
this.blur.emit();
327278
});
328279

329-
// Force setter to be called after editor initialized.
330-
this.theme = this._theme;
331-
this.placeholder = this._placeholder;
332-
this.readonly = this._readonly;
333-
this.indentWithTab = this._indentWithTab;
334-
this.indentUnit = this._indentUnit;
335-
this.lineWrapping = this._lineWrapping;
336-
this.highlightWhitespace = this._highlightWhitespace;
337-
this.setDisabledState(this.disabled);
338-
this.setLanguage(this.language, true);
280+
this.setEditable(!this.disabled);
281+
this.setReadonly(this.readonly);
282+
this.setTheme(this.theme);
283+
this.setPlaceholder(this.placeholder);
284+
this.setIndentWithTab(this.indentWithTab);
285+
this.setIndentUnit(this.indentUnit);
286+
this.setLineWrapping(this.lineWrapping);
287+
this.setHighlightWhitespace(this.highlightWhitespace);
288+
this.setLanguage(this.language);
339289
}
340290

341291
ngOnDestroy(): void {
@@ -358,6 +308,99 @@ export class CodeEditor implements OnInit, OnDestroy, ControlValueAccessor {
358308

359309
setDisabledState(isDisabled: boolean) {
360310
this.disabled = isDisabled;
361-
this._dispatchEffects(this._disabledConf.reconfigure(EditorView.editable.of(!isDisabled)));
311+
this.setEditable(!isDisabled);
312+
}
313+
314+
private _dispatchEffects(effects: StateEffect<any> | readonly StateEffect<any>[]) {
315+
return this.view?.dispatch({ effects });
316+
}
317+
318+
/** Sets the root extensions of the editor. */
319+
setExtensions(value: Extension[]) {
320+
this._dispatchEffects(StateEffect.reconfigure.of(value));
321+
}
322+
323+
/** Sets editor's value. */
324+
setValue(value: string) {
325+
this.view?.dispatch({
326+
changes: { from: 0, to: this.view.state.doc.length, insert: value },
327+
});
328+
}
329+
330+
/** Sets editor's editable state. */
331+
setEditable(value: boolean) {
332+
this._dispatchEffects(this._editableConf.reconfigure(EditorView.editable.of(value)));
333+
}
334+
335+
/** Sets editor's readonly state. */
336+
setReadonly(value: boolean) {
337+
this._dispatchEffects(this._readonlyConf.reconfigure(EditorState.readOnly.of(value)));
338+
}
339+
340+
/** Sets editor's theme. */
341+
setTheme(value: Theme) {
342+
this._dispatchEffects(
343+
this._themeConf.reconfigure(value === 'light' ? [] : value === 'dark' ? oneDark : value)
344+
);
345+
}
346+
347+
/** Sets editor's placeholder. */
348+
setPlaceholder(value: string) {
349+
this._dispatchEffects(this._placeholderConf.reconfigure(value ? placeholder(value) : []));
350+
}
351+
352+
/** Sets editor' indentWithTab. */
353+
setIndentWithTab(value: boolean) {
354+
this._dispatchEffects(
355+
this._indentWithTabConf.reconfigure(value ? keymap.of([indentWithTab]) : [])
356+
);
357+
}
358+
359+
/** Sets editor's indentUnit. */
360+
setIndentUnit(value: string) {
361+
this._dispatchEffects(this._indentUnitConf.reconfigure(value ? indentUnit.of(value) : []));
362+
}
363+
364+
/** Sets editor's lineWrapping. */
365+
setLineWrapping(value: boolean) {
366+
this._dispatchEffects(this._lineWrappingConf.reconfigure(value ? EditorView.lineWrapping : []));
367+
}
368+
369+
/** Sets editor's highlightWhitespace. */
370+
setHighlightWhitespace(value: boolean) {
371+
this._dispatchEffects(
372+
this._highlightWhitespaceConf.reconfigure(value ? highlightWhitespace() : [])
373+
);
374+
}
375+
376+
/** Sets editor's language dynamically. */
377+
setLanguage(lang: string) {
378+
if (!lang) {
379+
return;
380+
}
381+
if (this.languages.length === 0) {
382+
if (this.view) {
383+
console.error('No supported languages. Please set the `languages` prop at first.');
384+
}
385+
return;
386+
}
387+
const langDesc = this._findLanguage(lang);
388+
langDesc?.load().then(lang => {
389+
this._dispatchEffects(this._languageConf.reconfigure([lang]));
390+
});
391+
}
392+
393+
/** Find the language's extension by its name. Case insensitive. */
394+
private _findLanguage(name: string) {
395+
for (const lang of this.languages) {
396+
for (const alias of [lang.name, ...lang.alias]) {
397+
if (name.toLowerCase() === alias.toLowerCase()) {
398+
return lang;
399+
}
400+
}
401+
}
402+
console.error('Language not found:', name);
403+
console.info('Supported language names:', this.languages.map(lang => lang.name).join(', '));
404+
return null;
362405
}
363406
}

0 commit comments

Comments
 (0)