import cp = require ( 'child_process' ) ; const child_process = global . require ( 'child_process' ) ; import NvimClient = require ( 'promised-neovim-client' ) ; const attach = ( global . require ( 'promised-neovim-client' ) as typeof NvimClient ) . attach ; import Action = require ( './actions' ) ; import NeovimStore from './store' ; import log from '../log' ; export type RPCValue = NvimClient . Buffer | NvimClient . Window | NvimClient . Tabpage | number | boolean | string | any [ ] | { [ key ] : any ; } ; export default class NeovimProcess { neovim_process : cp . ChildProcess ; client : NvimClient . Nvim ; started : boolean ; constructor ( private readonly store , public command , public argv ) { this . started = false ; this . argv . unshift ( '--embed' ) ; } attach ( lines , columns ) { let err = null ; this . client = null ; this . neovim_process = child_process . spawn ( this . command , this . argv , { stdio : [ 'pipe' , 'pipe' , process . stderr ] } ) ; this . neovim_process . on ( 'error' , ( e ) => { err = e ; } ) ; if ( err || this . neovim_process . pid === undefined ) { return Promise . reject ( err || new Error ( 'Failed to spawn process: ' + this . command ) ) ; } return attach ( this . neovim_process . stdin , this . neovim_process . stdout ) . then ( nvim => { this . client = nvim ; nvim . on ( 'request' , this . onRequested . bind ( this ) ) ; nvim . on ( 'notification' , this . onNotified . bind ( this ) ) ; nvim . on ( 'disconnect' , this . onDisconnected . bind ( this ) ) ; nvim . uiAttach ( columns , lines , true , true ) ; this . started = true ; log . info ( `nvim attached: ${ this . neovim_process . pid } ${ lines }x${ columns } ${ JSON . stringify ( this . argv ) }` ) ; this . store . on ( 'input' , ( i ) => nvim . input ( i ) ) ; this . store . on ( 'update-screen-bounds' , ( ) => nvim . uiTryResize ( this . store . size . cols , this . store . size . lines ) ) ; this . client . command ( 'doautocmd <nomodeline> GUIEnter' , true ) ; } ) ; } onRequested ( method , args , response ) { log . info ( 'requested: ' , method , args , response ) ; } onNotified ( method , args ) { if ( method === 'redraw' ) { this . redraw ( args as RPCValue [ ] [ ] ) ; } else { log . debug ( 'Unknown method' , method , args ) ; } } onDisconnected ( ) { log . info ( 'disconnected: ' + this . neovim_process . pid ) ; this . started = false ; } finalize ( ) { return this . client . uiDetach ( ) . then ( ( ) => { this . client . quit ( ) ; this . started = false ; } ) ; } private redraw ( events ) { const d = this . store . dispatcher ; for ( const e of events ) { const name = e [ 0 ] as string ; const args = e [ 1 ] as RPCValue [ ] ; switch ( name ) { case 'put' : e . shift ( ) ; if ( e . length !== 0 ) { d . dispatch ( Action . putText ( e as string [ ] [ ] ) ) ; } break ; case 'cursor_goto' : d . dispatch ( Action . cursor ( args [ 0 ] as number , args [ 1 ] as number ) ) ; break ; case 'highlight_set' : e . shift ( ) ; const highlights = [ ] . concat . apply ( [ ] , e ) as Action . HighlightSet [ ] ; highlights . unshift ( { } ) ; const merged_highlight = Object . assign . apply ( Object , highlights ) as Action . HighlightSet ; d . dispatch ( Action . highlight ( merged_highlight ) ) ; break ; case 'clear' : d . dispatch ( Action . clearAll ( ) ) ; break ; case 'eol_clear' : d . dispatch ( Action . clearEndOfLine ( ) ) ; break ; case 'scroll' : d . dispatch ( Action . scrollScreen ( args [ 0 ] as number ) ) ; break ; case 'set_scroll_region' : d . dispatch ( Action . setScrollRegion ( { top : args [ 0 ] as number , bottom : args [ 1 ] as number , left : args [ 2 ] as number , right : args [ 3 ] as number , } ) ) ; break ; case 'resize' : d . dispatch ( Action . resize ( args [ 1 ] as number , args [ 0 ] as number ) ) ; break ; case 'update_fg' : d . dispatch ( Action . updateForeground ( args [ 0 ] as number ) ) ; break ; case 'update_bg' : d . dispatch ( Action . updateBackground ( args [ 0 ] as number ) ) ; break ; case 'update_sp' : d . dispatch ( Action . updateSpecialColor ( args [ 0 ] as number ) ) ; break ; case 'mode_info_set' : const modeInfo = args [ 1 ] as Action . ModeInfo [ ] ; d . dispatch ( Action . modeInfo ( modeInfo . reduce ( ( set , info ) => { set [ info . name ] = info ; return set ; } , Object . create ( null ) ) ) ) ; break ; case 'mode_change' : d . dispatch ( Action . changeMode ( args [ 0 ] as string ) ) ; break ; case 'busy_start' : d . dispatch ( Action . startBusy ( ) ) ; break ; case 'busy_stop' : d . dispatch ( Action . stopBusy ( ) ) ; break ; case 'mouse_on' : d . dispatch ( Action . enableMouse ( ) ) ; break ; case 'mouse_off' : d . dispatch ( Action . disableMouse ( ) ) ; break ; case 'bell' : d . dispatch ( Action . bell ( false ) ) ; break ; case 'visual_bell' : d . dispatch ( Action . bell ( true ) ) ; break ; case 'set_title' : d . dispatch ( Action . setTitle ( args [ 0 ] as string ) ) ; break ; case 'set_icon' : d . dispatch ( Action . setIcon ( args [ 0 ] as string ) ) ; break ; default : log . warn ( 'Unhandled event: ' + name , args ) ; break ; } } } }