import { Injectable , ElementRef , Optional , Inject , NgZone } from '@angular/core' ; import { Observable , Subject } from 'rxjs' ; import { shareReplay , take , map , takeUntil , tap } from 'rxjs/operators' ; import { fromDisposable } from '../utils/observable/from-disposable' ; import { COMPLETION_PROVIDERS } from '../tokens/completion-provider.token' ; import { MONACO_EDITOR_OPTIONS } from '../tokens/editor-options.token' ; import { CompletionItemProvider } from '../entities/completion-item-provider' ; import { MonacoEditorOptions } from '../entities/editor-options' ; import { MonacoFile } from '../entities/file' ; declare const window ; @ Injectable ( ) export class MonacoEditorService { private file : MonacoFile ; private monacoEditor : monaco . editor . IEditor ; private onFileChange = new Subject < MonacoFile > ( ) ; fileChange$ = this . onFileChange . asObservable ( ) ; bootstrap$ = Observable . create ( ( observer ) => { const script = document . createElement ( 'script' ) ; script . type = 'text/javascript' ; script . src = 'libs/vs/loader.js' ; script . onload = ( ) => { window . require . config ( { paths : { vs : 'libs/vs' } } ) ; window . require ( [ 'vs/editor/editor.main' ] , ( ) => { observer . next ( ) ; } ) ; } ; document . body . appendChild ( script ) ; } ) . pipe ( shareReplay ( 1 ) ) ; constructor ( @ Optional ( ) @ Inject ( COMPLETION_PROVIDERS ) private completionProviders , @ Optional ( ) @ Inject ( MONACO_EDITOR_OPTIONS ) private editorOptions , private zone ) { } get editor ( ) { return this . monacoEditor ; } private registerCompletionProviders ( ) { if ( ! this . completionProviders ) { return ; } for ( const completionProvider of this . completionProviders ) { monaco . languages . registerCompletionItemProvider ( completionProvider . language , completionProvider ) ; } } private registerModelChangeListener ( file , model ) { const destroy = fromDisposable ( model . onWillDispose . bind ( model ) ) . pipe ( take ( 1 ) ) ; fromDisposable ( model . onDidChangeContent . bind ( model ) ) . pipe ( map ( ( ) => model . getValue ( ) ) , takeUntil ( destroy ) ) . subscribe ( ( content ) => { this . zone . run ( ( ) => { this . onFileChange . next ( { ... file , content } ) ; } ) ; } ) ; } /**
     * Dispose all editor models
     */ disposeModels ( ) { if ( ! window . monaco ) { return ; } for ( const model of monaco . editor . getModels ( ) ) { model . dispose ( ) ; } } param container param options load ( container , options = { } ) { const editorOptions = options . editor || this . editorOptions || { } ; return this . bootstrap$ . pipe ( tap ( ( ) => { this . disposeModels ( ) ; this . monacoEditor = monaco . editor . create ( container . nativeElement , { theme : options . theme , ... editorOptions } ) ; this . registerCompletionProviders ( ) ; if ( this . file ) { this . open ( this . file ) ; } } ) ) ; } param file open ( file ) { this . file = file ; if ( ! this . monacoEditor ) { return ; } const uri = monaco . Uri . file ( file . uri ) ; let model = monaco . editor . getModel ( uri ) ; if ( model ) { if ( file . language && model . getModeId ( ) !== file . language ) { model . dispose ( ) ; model = undefined ; } else { model . setValue ( file . content ) ; } } if ( ! model ) { model = monaco . editor . createModel ( file . content , file . language , uri ) ; this . registerModelChangeListener ( file , model ) ; } this . monacoEditor . setModel ( model ) ; } param options updateOptions ( options ) { if ( this . monacoEditor ) { this . monacoEditor . updateOptions ( options ) ; } } param theme setTheme ( theme ) { if ( window . monaco ) { monaco . editor . setTheme ( theme ) ; } } }