import { MasterControl as Nvim } from './master-control' ; import { RedrawEvents } from '../../common/ipc' ; import { WinPosWinInfo , WinFloatPosWinInfo , CursorShape , Mode , PopupMenu , MessageKind , CommandType , CommandUpdate , } from '../../common/types' ; import { normalizeVimMode } from '../../common/neovim-utils' ; import { BrowserWindow } from 'electron' ; type SendFunc = ( event : typeof RedrawEvents [ keyof typeof RedrawEvents ] , ... args : any [ ] ) => void ; interface CommandLineCache { cmd ? : string ; active : boolean ; position : number ; } const cmdcache = { active : false , position : - 999 , } ; const doNotUpdateCmdlineIfSame = ( args ) => { const cmdlineIsSame = ( ... args ) => cmdcache . active && cmdcache . position === args [ 1 ] ; if ( ! args || ! Array . isArray ( args ) ) return false ; const [ cmd , data ] = args ; if ( cmd !== 'cmdline_show' ) return false ; return cmdlineIsSame ( ... data ) ; } ; type CmdContent = [ any , string ] ; type CmdlineShow = [ CmdContent [ ] , number , string , string , number , number ] ; type CmdlineShowEvent = [ any , CmdlineShow ] ; let currentCommandMode ; const cmdline_show = ( [ , [ content , position , str1 , str2 , indent , level ] ] , send ) => { const opChar = str1 ; const prompt = str2 ; cmdcache . active = true ; cmdcache . position = position ; const cmd = content . reduce ( ( str , [ _ , item ] ) => str + item , '' ) ; if ( cmdcache . cmd === cmd ) return ; cmdcache . cmd = cmd ; const kind = Reflect . get ( { ':' : CommandType . Ex , '/' : CommandType . SearchForward , '?' : CommandType . SearchBackward , } , opChar ) || CommandType . Ex ; currentCommandMode = kind ; const cmdPrompt = kind === CommandType . Ex ; const searchPrompt = kind === CommandType . SearchForward || kind === CommandType . SearchBackward ; if ( cmdPrompt ) send ( RedrawEvents . cmdUpdate , { cmd , prompt , kind : prompt ? CommandType . Prompt : kind , position , } as CommandUpdate ) ; else if ( searchPrompt ) send ( RedrawEvents . searchUpdate , { cmd , prompt , kind : prompt ? CommandType . Prompt : kind , position , } as CommandUpdate ) ; indent && console . log ( 'indent:' , indent ) ; level > 1 && console . log ( 'level:' , level ) ; } ; const cmdline_hide = ( send ) => { Object . assign ( cmdcache , { active : false , position : - 999 , cmd : undefined } ) ; send ( RedrawEvents . cmdHide ) ; send ( RedrawEvents . searchHide ) ; } ; const cmdline_pos = ( [ , [ position ] ] , send ) => { if ( currentCommandMode === CommandType . Ex ) send ( RedrawEvents . cmdUpdate , { position } ) ; else send ( RedrawEvents . searchUpdate , { position } ) ; } ; const win_pos = ( e , send ) => { const count = e . length ; const windows = [ ] ; for ( let ix = 1 ; ix < count ; ix ++ ) { const [ gridId , { data : winId } , row , col , width , height ] = e [ ix ] ; windows . push ( { gridId , winId , row , col , width , height } ) ; } send ( RedrawEvents . winPos , windows ) ; } ; const win_float_pos = ( e , send ) => { const count = e . length ; const windows = [ ] ; for ( let ix = 1 ; ix < count ; ix ++ ) { const [ gridId , { data : winId } , anchor , anchorGrid , anchorRow , anchorCol , ] = e [ ix ] ; windows . push ( { gridId , winId , anchor , anchorGrid , anchorRow , anchorCol } ) ; } send ( RedrawEvents . winFloatPos , windows ) ; } ; const tabline_update = ( [ , [ curtab , tabs ] ] , send ) => { send ( RedrawEvents . tablineUpdate , { curtab : { data : curtab . data , name : curtab . name } , tabs : tabs . map ( ( tab ) => ( { data : tab . data , name : tab . name } ) ) , } ) ; } ; const modes = new Map < string , Mode > ( ) ; const mode_change = ( [ , [ m ] ] , nvim , send ) => { nvim . instanceApi . setMode ( normalizeVimMode ( m ) ) ; const info = modes . get ( m ) ; if ( info ) { send ( RedrawEvents . modeChange , info ) ; } } ; interface ModeInfo { blinkoff ? : number ; blinkon ? : number ; blinkwait ? : number ; cell_percentage ? : number ; cursor_shape ? : string ; attr_id ? : number ; attr_id_lm ? : number ; hl_id ? : number ; id_lm ? : number ; mouse_shape ? : number ; name : string ; short_name : string ; } const cursorShapeType = ( shape ? ) => { if ( shape === 'block' ) return CursorShape . block ; if ( shape === 'horizontal' ) return CursorShape . underline ; if ( shape === 'vertical' ) return CursorShape . line ; else return CursorShape . block ; } ; const mode_info_set = ( [ , [ , infos ] ] ) => infos . forEach ( ( m ) => { const info = { shape : cursorShapeType ( m . cursor_shape ) , size : m . cell_percentage , hlid : m . attr_id , } ; modes . set ( m . name , info ) ; } ) ; const popupmenu_show = ( [ , [ itemz , index , row , col , grid ] ] , send ) => { const items = itemz . map ( ( m ) => { const [ word , kind , menu , info ] = m ; return { word , kind , menu , info } ; } ) ; const data = { row , col , grid , index , items } ; send ( RedrawEvents . pmenuShow , data ) ; } ; const messageNotifyKindMappings = new Map ( [ [ 'echo' , MessageKind . Info ] , [ 'emsg' , MessageKind . Error ] , [ 'echoerr' , MessageKind . Error ] , [ 'echomsg' , MessageKind . Info ] , [ 'quickfix' , MessageKind . System ] , [ 'return_prompt' , MessageKind . System ] , ] ) ; const showStatusMessage = ( message , send ) => { const newlineCount = ( message . match ( /\n/g ) || [ ] ) . length ; if ( newlineCount ) return send ( RedrawEvents . msgShow , { message , kind : MessageKind . Info } ) ; send ( RedrawEvents . msgStatus , message ) ; } ; const state = { messagePromptVisible : false , lastMessageTime : Date . now ( ) , } ; type MessageEvent = [ number , string ] ; const msg_show = ( [ , [ messageKind , msgs , replaceLast ] ] , cursorVisible , send ) => { const lastMessageTime = state . lastMessageTime ; state . lastMessageTime = Date . now ( ) ; const kind = messageNotifyKindMappings . get ( messageKind ) ; state . messagePromptVisible = ! cursorVisible ; const message = msgs . reduce ( ( res , [ , text ] ) => ( res += text ) , '' ) ; if ( ! kind ) { const timeDiff = Date . now ( ) - lastMessageTime ; const probablyNeedsToBeAppended = ! replaceLast && timeDiff < 100 ; if ( ! probablyNeedsToBeAppended ) return showStatusMessage ( message , send ) ; } const msginfo = { message , kind : kind || MessageKind . System , stealsFocus : ! cursorVisible , } ; replaceLast ? send ( RedrawEvents . msgShow , msginfo ) : send ( RedrawEvents . msgAppend , msginfo ) ; } ; type MessageHistory = [ string , [ number , string ] [ ] ] ; const msg_history_show = ( [ , [ messages ] ] , send ) => { const mappedMessages = messages . map ( ( [ messageKind , msgs ] ) => { const kind = messageNotifyKindMappings . get ( messageKind ) ; const message = msgs . reduce ( ( res , [ , text ] ) => ( res += text ) , '' ) ; return { message , kind : kind || MessageKind . Info } ; } ) ; send ( RedrawEvents . msgShowHistory , mappedMessages ) ; } ; const msg_showmode = ( [ , [ msgs ] ] , send ) => { if ( ! msgs . length ) return send ( RedrawEvents . msgControl , '' ) ; msgs . forEach ( ( [ , text ] ) => send ( RedrawEvents . msgControl , text ) ) ; } ; const msg_showcmd = ( [ , [ msgs ] ] , send ) => { if ( ! msgs . length ) return send ( RedrawEvents . msgControl , '' ) ; msgs . forEach ( ( [ , text ] ) => send ( RedrawEvents . msgControl , text ) ) ; } ; const msg_clear = ( [ , [ content ] ] , send ) => { send ( RedrawEvents . msgClear ) ; send ( RedrawEvents . msgStatus , content ) ; } ; const msg_ruler = ( _ ) => { } ; const messageClearPromptsMaybeHack = ( cursorVisible , send ) => { if ( ! state . messagePromptVisible ) return ; if ( cursorVisible ) send ( RedrawEvents . msgClear , 'stealsFocus' ) ; } ; const grid_cursor_goto = ( [ , [ gridId , row , col ] ] , send ) => { state_cursorVisible = gridId !== 1 ; if ( gridId === 1 ) return ; send ( RedrawEvents . gridCursorGoto , gridId , row , col ) ; } ; let state_cursorVisible = true ; export const handleRedraw = ( nvim , win , redrawEvents ) => { const sendToRenderer = ( channel , ... args ) => win . webContents . send ( channel , ... args ) ; if ( doNotUpdateCmdlineIfSame ( redrawEvents [ 0 ] ) ) return ; let winUpdates = false ; const messageEvents = [ ] ; const eventCount = redrawEvents . length ; for ( let ix = 0 ; ix < eventCount ; ix ++ ) { const ev = redrawEvents [ ix ] ; const e = ev [ 0 ] ; if ( e === 'grid_line' ) sendToRenderer ( RedrawEvents . gridLine , ev ) ; else if ( e === 'flush' ) winUpdates = true ; else if ( e === 'grid_scroll' ) sendToRenderer ( RedrawEvents . gridScroll , ev ) ; else if ( e === 'grid_cursor_goto' ) grid_cursor_goto ( ev , sendToRenderer ) ; else if ( e === 'win_pos' ) ( winUpdates = true ) , win_pos ( ev , sendToRenderer ) ; else if ( e === 'win_float_pos' ) ( winUpdates = true ) , win_float_pos ( ev , sendToRenderer ) ; else if ( e === 'win_close' ) sendToRenderer ( RedrawEvents . winClose , ev [ 1 ] ) ; else if ( e === 'win_hide' ) sendToRenderer ( RedrawEvents . winHide , ev ) ; else if ( e === 'grid_resize' ) ( winUpdates = true ) , sendToRenderer ( RedrawEvents . gridResize , ev ) ; else if ( e === 'grid_clear' ) sendToRenderer ( RedrawEvents . gridClear , ev ) ; else if ( e === 'grid_destroy' ) sendToRenderer ( RedrawEvents . gridDestroy , ev ) ; else if ( e === 'tabline_update' ) tabline_update ( ev , sendToRenderer ) ; else if ( e === 'mode_change' ) mode_change ( ev , nvim , sendToRenderer ) ; else if ( e === 'popupmenu_hide' ) sendToRenderer ( RedrawEvents . pmenuHide ) ; else if ( e === 'popupmenu_select' ) sendToRenderer ( RedrawEvents . pmenuSelect , ev [ 1 ] [ 0 ] ) ; else if ( e === 'popupmenu_show' ) popupmenu_show ( ev , sendToRenderer ) ; else if ( e === 'cmdline_show' ) cmdline_show ( ev , sendToRenderer ) ; else if ( e === 'cmdline_pos' ) cmdline_pos ( ev , sendToRenderer ) ; else if ( e === 'cmdline_hide' ) cmdline_hide ( sendToRenderer ) ; else if ( e === 'hl_attr_define' ) sendToRenderer ( RedrawEvents . hlAttrDefine , ev ) ; else if ( e === 'default_colors_set' ) sendToRenderer ( RedrawEvents . defaultColorsSet , ev ) ; else if ( e === 'option_set' ) sendToRenderer ( RedrawEvents . optionSet , ev ) ; else if ( e === 'mode_info_set' ) mode_info_set ( ev ) ; else if ( e === 'wildmenu_show' ) sendToRenderer ( RedrawEvents . wildmenuShow , ev [ 1 ] [ 0 ] ) ; else if ( e === 'wildmenu_select' ) sendToRenderer ( RedrawEvents . wildmenuSelect , ev [ 1 ] [ 0 ] ) ; else if ( e === 'wildmenu_hide' ) sendToRenderer ( RedrawEvents . wildmenuHide ) ; else if ( e . startsWith ( 'msg_' ) ) messageEvents . push ( ev ) ; else if ( e === 'set_title' ) sendToRenderer ( RedrawEvents . setTitle , ev [ 1 ] [ 0 ] ) ; else if ( e === 'busy_start' ) sendToRenderer ( RedrawEvents . hideThenDisableCursor ) ; else if ( e === 'busy_stop' ) sendToRenderer ( RedrawEvents . enableThenShowCursor ) ; else if ( e === 'win_viewport' ) { Object . assign ( nvim . instanceApi . state , { line : ev [ 1 ] [ 4 ] , column : ev [ 1 ] [ 5 ] , editorTopLine : ev [ 1 ] [ 2 ] , editorBottomLine : ev [ 1 ] [ 3 ] , } ) ; } } const messageEventsCount = messageEvents . length ; for ( let ix = 0 ; ix < messageEventsCount ; ix ++ ) { const ev = messageEvents [ ix ] ; const e = ev [ 0 ] ; if ( e === 'msg_show' ) msg_show ( ev , state_cursorVisible , sendToRenderer ) ; else if ( e === 'msg_showmode' ) msg_showmode ( ev , sendToRenderer ) ; else if ( e === 'msg_showcmd' ) msg_showcmd ( ev , sendToRenderer ) ; else if ( e === 'msg_history_show' ) msg_history_show ( ev , sendToRenderer ) ; else if ( e === 'msg_clear' ) msg_clear ( ev , sendToRenderer ) ; else if ( e === 'msg_ruler' ) msg_ruler ( ev ) ; } messageClearPromptsMaybeHack ( state_cursorVisible , sendToRenderer ) ; state_cursorVisible ? sendToRenderer ( RedrawEvents . showCursor ) : sendToRenderer ( RedrawEvents . hideCursor ) ; sendToRenderer ( RedrawEvents . pubRedraw ) ; if ( ! winUpdates ) return ; sendToRenderer ( RedrawEvents . disposeInvalidWinsThenLayout ) ; } ;