@@ -53,14 +53,27 @@ const External = Annotation.define<boolean>();
5353} )
5454export 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