/********************************************************************************
 * Copyright (C) 2017 TypeFox and others.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License v. 2.0 are satisfied: GNU General Public License, version 2
 * with the GNU Classpath Exception which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 ********************************************************************************/ import { Disposable } from './disposable' ; import { MaybePromise } from './types' ; /**
 * Represents a typed event.
 */ export interface Event < T > { param listener param thisArgs param disposables return ( listener , thisArgs ? , disposables ? ) : Disposable ; /**
     * An emitter will print a warning if more listeners are added for this event.
     * The event.maxListeners allows the limit to be modified for this specific event.
     * The value can be set to 0 to indicate an unlimited number of listener.
     */ maxListeners : number ; } export namespace Event { const _disposable = { dispose ( ) { } } ; export const None = Object . assign ( function ( ) : { dispose ( ) : void ; } { return _disposable ; } , { get maxListeners ( ) : number { return 0 ; } , set maxListeners ( maxListeners ) { } } ) ; /**
     * Given an event and a `map` function, returns another event which maps each element
     * through the mapping function.
     */ export function map < I , O > ( event , mapFunc ) { return Object . assign ( ( listener , thisArgs ? , disposables ? ) => event ( i => listener . call ( thisArgs , mapFunc ( i ) ) , undefined , disposables ) , { maxListeners : 0 , } ) ; } } type Callback = ( ... args : any [ ] ) => any ; class CallbackList implements Iterable < Callback > { private _callbacks : Function [ ] | undefined ; private _contexts : any [ ] | undefined ; get length ( ) : number { return this . _callbacks && this . _callbacks . length || 0 ; } public add ( callback , context = undefined , bucket ? ) { if ( ! this . _callbacks ) { this . _callbacks = [ ] ; this . _contexts = [ ] ; } this . _callbacks . push ( callback ) ; this . _contexts ! . push ( context ) ; if ( Array . isArray ( bucket ) ) { bucket . push ( { dispose : ( ) => this . remove ( callback , context ) } ) ; } } public remove ( callback , context = undefined ) { if ( ! this . _callbacks ) { return ; } let foundCallbackWithDifferentContext = false ; for ( let i = 0 ; i < this . _callbacks . length ; i ++ ) { if ( this . _callbacks [ i ] === callback ) { if ( this . _contexts ! [ i ] === context ) { this . _callbacks . splice ( i , 1 ) ; this . _contexts ! . splice ( i , 1 ) ; return ; } else { foundCallbackWithDifferentContext = true ; } } } if ( foundCallbackWithDifferentContext ) { throw new Error ( 'When adding a listener with a context, you should remove it with the same context' ) ; } } public [ Symbol . iterator ] ( ) { if ( ! this . _callbacks ) { return [ ] [ Symbol . iterator ] ( ) ; } const callbacks = this . _callbacks . slice ( 0 ) ; const contexts = this . _contexts ! . slice ( 0 ) ; return callbacks . map ( ( callback , i ) => ( ... args ) => callback . apply ( contexts [ i ] , args ) ) [ Symbol . iterator ] ( ) ; } public invoke ( ... args ) { const ret = [ ] ; for ( const callback of this ) { try { ret . push ( callback ( ... args ) ) ; } catch ( e ) { console . error ( e ) ; } } return ret ; } public isEmpty ( ) { return ! this . _callbacks || this . _callbacks . length === 0 ; } public dispose ( ) { this . _callbacks = undefined ; this . _contexts = undefined ; } } export interface EmitterOptions { onFirstListenerAdd ? : Function ; onLastListenerRemove ? : Function ; } export class Emitter < T = any > { private static LEAK_WARNING_THRESHHOLD = 175 ; private static _noop = function ( ) : void { } ; private _event : Event < T > ; protected _callbacks : CallbackList | undefined ; private _disposed = false ; private _leakingStacks : Map < string , number > | undefined ; private _leakWarnCountdown = 0 ; constructor ( private _options ? ) { } /**
     * For the public to allow to subscribe
     * to events from this Emitter
     */ get event ( ) : Event < T > { if ( ! this . _event ) { this . _event = Object . assign ( ( listener , thisArgs ? , disposables ? ) => { if ( ! this . _callbacks ) { this . _callbacks = new CallbackList ( ) ; } if ( this . _options && this . _options . onFirstListenerAdd && this . _callbacks . isEmpty ( ) ) { this . _options . onFirstListenerAdd ( this ) ; } this . _callbacks . add ( listener , thisArgs ) ; const removeMaxListenersCheck = this . checkMaxListeners ( this . _event . maxListeners ) ; const result = { dispose : ( ) => { if ( removeMaxListenersCheck ) { removeMaxListenersCheck ( ) ; } result . dispose = Emitter . _noop ; if ( ! this . _disposed ) { this . _callbacks ! . remove ( listener , thisArgs ) ; result . dispose = Emitter . _noop ; if ( this . _options && this . _options . onLastListenerRemove && this . _callbacks ! . isEmpty ( ) ) { this . _options . onLastListenerRemove ( this ) ; } } } } ; if ( Array . isArray ( disposables ) ) { disposables . push ( result ) ; } return result ; } , { maxListeners : Emitter . LEAK_WARNING_THRESHHOLD } ) ; } return this . _event ; } protected checkMaxListeners ( maxListeners ) { if ( maxListeners === 0 || ! this . _callbacks ) { return undefined ; } const listenerCount = this . _callbacks . length ; if ( listenerCount <= maxListeners ) { return undefined ; } const popStack = this . pushLeakingStack ( ) ; this . _leakWarnCountdown -= 1 ; if ( this . _leakWarnCountdown <= 0 ) { this . _leakWarnCountdown = maxListeners * 0.5 ; let topStack ; let topCount = 0 ; this . _leakingStacks ! . forEach ( ( stackCount , stack ) => { if ( ! topStack || topCount < stackCount ) { topStack = stack ; topCount = stackCount ; } } ) ; console . warn ( `Possible Emitter memory leak detected. ${ listenerCount } listeners added. Use event.maxListeners to increase the limit (${ maxListeners }). MOST frequent listener (${ topCount }):` ) ; console . warn ( topStack ! ) ; } return popStack ; } protected pushLeakingStack ( ) { if ( ! this . _leakingStacks ) { this . _leakingStacks = new Map ( ) ; } const stack = new Error ( ) . stack ! . split ( '\n' ) . slice ( 3 ) . join ( '\n' ) ; const count = ( this . _leakingStacks . get ( stack ) || 0 ) ; this . _leakingStacks . set ( stack , count + 1 ) ; return ( ) => this . popLeakingStack ( stack ) ; } protected popLeakingStack ( stack ) { if ( ! this . _leakingStacks ) { return ; } const count = ( this . _leakingStacks . get ( stack ) || 0 ) ; this . _leakingStacks . set ( stack , count - 1 ) ; } /**
     * To be kept private to fire an event to
     * subscribers
     */ fire ( event ) { if ( this . _callbacks ) { this . _callbacks . invoke ( event ) ; } } /**
     * Process each listener one by one.
     * Return `false` to stop iterating over the listeners, `true` to continue.
     */ async sequence ( processor ) { if ( this . _callbacks ) { for ( const listener of this . _callbacks ) { if ( ! await processor ( listener ) ) { break ; } } } } dispose ( ) { if ( this . _leakingStacks ) { this . _leakingStacks . clear ( ) ; this . _leakingStacks = undefined ; } if ( this . _callbacks ) { this . _callbacks . dispose ( ) ; this . _callbacks = undefined ; } this . _disposed = true ; } } export interface WaitUntilEvent { param thenable waitUntil ( thenable ) : void ; } export namespace WaitUntilEvent { /**
     * Fire all listeners in the same tick.
     *
     * Use `AsyncEmitter.fire` to fire listeners async one after another.
     */ export async function fire < T extends WaitUntilEvent > ( emitter , event , timeout = undefined ) { const waitables = [ ] ; const asyncEvent = Object . assign ( event , { waitUntil : ( thenable ) => { if ( Object . isFrozen ( waitables ) ) { throw new Error ( 'waitUntil cannot be called asynchronously.' ) ; } waitables . push ( thenable ) ; } } ) as T ; try { emitter . fire ( asyncEvent ) ; Object . freeze ( waitables ) ; } finally { delete asyncEvent [ 'waitUntil' ] ; } if ( ! waitables . length ) { return ; } if ( timeout !== undefined ) { await Promise . race ( [ Promise . all ( waitables ) , new Promise ( resolve => setTimeout ( resolve , timeout ) ) ] ) ; } else { await Promise . all ( waitables ) ; } } } import { CancellationToken } from './cancellation' ; export class AsyncEmitter < T extends WaitUntilEvent > extends Emitter < T > { protected deliveryQueue : Promise < void > | undefined ; /**
     * Fire listeners async one after another.
     */ fire ( event , token = CancellationToken . None , promiseJoin ? ) { const callbacks = this . _callbacks ; if ( ! callbacks ) { return Promise . resolve ( ) ; } const listeners = [ ... callbacks ] ; if ( this . deliveryQueue ) { return this . deliveryQueue = this . deliveryQueue . then ( ( ) => this . deliver ( listeners , event , token , promiseJoin ) ) ; } return this . deliveryQueue = this . deliver ( listeners , event , token , promiseJoin ) ; } protected async deliver ( listeners , event , token , promiseJoin ? ) { for ( const listener of listeners ) { if ( token . isCancellationRequested ) { return ; } const waitables = [ ] ; const asyncEvent = Object . assign ( event , { waitUntil : ( thenable ) => { if ( Object . isFrozen ( waitables ) ) { throw new Error ( 'waitUntil cannot be called asynchronously.' ) ; } if ( promiseJoin ) { thenable = promiseJoin ( thenable , listener ) ; } waitables . push ( thenable ) ; } } ) as T ; try { listener ( event ) ; Object . freeze ( waitables ) ; } catch ( e ) { console . error ( e ) ; } finally { delete asyncEvent [ 'waitUntil' ] ; } if ( ! waitables . length ) { return ; } try { await Promise . all ( waitables ) ; } catch ( e ) { console . error ( e ) ; } } } }