import { Panel } from "atom" ; import * as SelectListView from "atom-select-list" ; import { EventEmitter } from "events" ; export template TItem param { string } message param { Array < IMenuItem < TItem > > } items param { number } defaultIndex returns { Promise < IInputMenuResult < TItem > > } export async function getSingleMenuSelection < TItem > ( message , items , defaultIndex = null ) { if ( defaultIndex !== null && ( defaultIndex > items . length - 1 || defaultIndex < 0 ) ) { throw new RangeError ( ) ; } const result = await new SingleItemSelectMenu < TItem > ( message , items ) . getResult ( ) ; if ( result . reason === InputMenuResultReason . Cancelled ) { return result ; } if ( defaultIndex === null ) { return result ; } if ( result . value === null || result . value === undefined ) { result . value = items [ defaultIndex ] . value ; } return result ; } export interface template TItem export interface IMenuItem < TItem > { value : TItem ; name : string ; display : string ; } export interface template TItem export interface IInputMenuResult < TItem > { reason : InputMenuResultReason ; value : TItem ; } export enum { number } export enum InputMenuResultReason { Completed , Cancelled } abstract class template TItem template TResult abstract class ItemSelectMenuBase < TItem , TResult > { protected panel : ItemSelectMenuPanel < TItem > ; protected transformElement : ( element : HTMLElement , item : IMenuItem < TItem > ) => HTMLElement ; private emitter : EventEmitter ; constructor ( message , items ) { this . emitter = new EventEmitter ( ) ; this . panel = new ItemSelectMenuPanel < TItem > ( { didCancelSelection : ( ) => this . cancel ( ) , didConfirmSelection : ( item ) => this . onItemSelected ( item ) , elementForItem : ( item ) => this . createElementForItem ( item ) , infoMessage : message , items , } ) ; } returns { Promise < IInputMenuResult < TResult > > } memberof public async getResult ( ) { try { this . panel . attach ( ) ; return await this . waitForCompletion ( ) ; } finally { this . closeMenu ( ) ; } } protected abstract param { IMenuItem < TItem > } item memberof protected abstract onItemSelected ( item ) ; protected param { TResult } result memberof protected complete ( result ) { this . emitter . emit ( "menuSelectCompleted" , { reason : InputMenuResultReason . Completed , value : result , } as IInputMenuResult < TResult > ) ; } protected memberof protected cancel ( ) { this . emitter . emit ( "menuSelectCompleted" , { reason : InputMenuResultReason . Cancelled , result : null , } ) ; } private closeMenu ( ) { this . panel . dispose ( ) ; } private maybeTransformElement ( element , item ) { if ( this . transformElement === null || this . transformElement === undefined ) { return element ; } return this . transformElement ( element , item ) ; } private createElementForItem ( item ) { const listItem = document . createElement ( "li" ) ; const key = document . createElement ( "span" ) ; const keyContent = document . createElement ( "strong" ) ; keyContent . textContent = item . name ; key . appendChild ( keyContent ) ; listItem . appendChild ( key ) ; if ( item . display === null || item . display === undefined || item . display === "" ) { return this . maybeTransformElement ( listItem , item ) ; } const description = document . createElement ( "span" ) ; const descriptionContent = document . createElement ( "small" ) ; descriptionContent . textContent = " " + item . display ; description . appendChild ( descriptionContent ) ; listItem . appendChild ( description ) ; return this . maybeTransformElement ( listItem , item ) ; } private waitForCompletion ( ) { return new Promise ( ( resolve , reject ) => { this . emitter . once ( "menuSelectCompleted" , ( result ) => resolve ( result ) ) ; } ) ; } } class extends ItemSelectMenuBase < TItem , TItem > template TItem class SingleItemSelectMenu < TItem > extends ItemSelectMenuBase < TItem , TItem > { protected param { IMenuItem < TItem > } item memberof protected onItemSelected ( item ) { this . complete ( item . value ) ; } } interface template TItem interface IItemSelectMenuPanelArguments < TItem > { didCancelSelection : ( ) => void ; didConfirmSelection : ( item : IMenuItem < TItem > ) => void ; elementForItem : ( item : IMenuItem < TItem > ) => HTMLElement ; infoMessage : string ; items : Array < IMenuItem < TItem > > ; } class template TItem class ItemSelectMenuPanel < TItem > { private view : SelectListView ; private activePanel : Panel ; private previouslyFocusedElement : HTMLElement ; constructor ( args ) { args = Object . assign ( args , { filterKeyForItem : ( item ) => item . name + item . display , } ) ; this . view = new SelectListView ( args ) ; } public get element ( ) : HTMLElement { return this . view . element ; } public dispose ( ) { this . activePanel . destroy ( ) ; this . view . destroy ( ) ; if ( this . previouslyFocusedElement !== null ) { this . previouslyFocusedElement . focus ( ) ; this . previouslyFocusedElement = null ; } } public attach ( ) { this . previouslyFocusedElement = document . activeElement as HTMLElement ; this . activePanel = atom . workspace . addModalPanel < ItemSelectMenuPanel < TItem > > ( { item : this } ) ; this . view . focus ( ) ; } }