import { throttle , isFinite , isEqual } from 'lodash' ; import { getColor , getColorNum } from 'src/lib/getColor' ; import * as PIXI from 'src/lib/pixi' ; import type { Settings } from 'src/types' ; import type { Nvim , Transport , UiEventsHandlers , UiEventsArgs , ModeInfo , HighlightAttrs , } from '@vvim/nvim' ; export type Screen = { screenCoords : ( width : number , height : number ) => [ number , number ] ; getCursorElement : ( ) => HTMLDivElement ; } ; type CalculatedProps = { bgColor : string ; fgColor : string ; spColor ? : string ; hiItalic : boolean ; hiBold : boolean ; hiUnderline : boolean ; hiUndercurl : boolean ; hiStrikethrough : boolean ; } ; type HighlightProps = { calculated ? : CalculatedProps ; value ? : HighlightAttrs ; } ; type HighlightTable = Record < number , HighlightProps > ; type Char = { sprite : PIXI . Sprite ; bg : PIXI . Sprite ; char ? : string | null ; hlId ? : number ; } ; const DEFAULT_FONT_FAMILY = 'monospace' ; const screen = ( { settings , transport , nvim , } ) : Screen => { let screenContainer ; let cursorEl ; let screenEl ; let cursorPosition = [ 0 , 0 ] ; let cursorChar ; let startCursorBlinkOnTimeout ; let startCursorBlinkOffTimeout ; let blinkOnCursorBlinkInterval ; let blinkOffCursorBlinkInterval ; let scale ; let charWidth ; let charHeight ; let fontFamily = DEFAULT_FONT_FAMILY ; let fontSize = 12 ; let lineHeight = 1.25 ; let letterSpacing = 0 ; const defaultFgColor = 'rgb(255,255,255)' ; const defaultBgColor = 'rgb(0,0,0)' ; const defaultSpColor = 'rgb(255,255,255)' ; let cols ; let rows ; let modeInfoSet ; let mode ; let showBold = true ; let showItalic = true ; let showUnderline = true ; let showUndercurl = true ; let showStrikethrough = true ; const charCanvas = new OffscreenCanvas ( 1 , 1 ) ; const charCtx = charCanvas . getContext ( '2d' , { alpha : true } ) as OffscreenCanvasRenderingContext2D ; const chars = [ ] ; const highlightTable = { '0' : { calculated : { bgColor : defaultBgColor , fgColor : defaultFgColor , spColor : defaultSpColor , hiItalic : false , hiBold : false , hiUnderline : false , hiUndercurl : false , hiStrikethrough : false , } , } , '-1' : { calculated : { bgColor : defaultFgColor , fgColor : defaultBgColor , spColor : defaultSpColor , hiItalic : false , hiBold : false , hiUnderline : false , hiUndercurl : false , hiStrikethrough : false , } , } , } ; let stage ; let renderer ; let charsContainer ; let bgContainer ; let cursorContainer ; let cursorSprite ; let cursorBg ; let needRerender = false ; const TARGET_FPS = 60 ; const getCursorElement = ( ) : HTMLDivElement => cursorEl ; const windowPixelSize = ( ) => ( { width : window . screen . width * window . devicePixelRatio , height : window . screen . height * window . devicePixelRatio , } ) ; const initCursor = ( ) => { cursorEl = document . createElement ( 'div' ) ; cursorEl . style . position = 'absolute' ; cursorEl . style . zIndex = '100' ; cursorEl . style . top = '0' ; cursorEl . style . left = '0' ; screenEl . appendChild ( cursorEl ) ; } ; const initScreen = ( ) => { screenContainer = document . createElement ( 'div' ) ; document . body . appendChild ( screenContainer ) ; screenContainer . style . position = 'absolute' ; screenContainer . style . left = '0' ; screenContainer . style . top = '0' ; screenContainer . style . transformOrigin = '0 0' ; screenEl = document . createElement ( 'div' ) ; screenEl . style . contain = 'strict' ; screenEl . style . overflow = 'hidden' ; const pixi = new PIXI . Application ( { transparent : true , autoStart : false , ... windowPixelSize ( ) , } ) ; screenEl . appendChild ( pixi . view ) ; screenContainer . appendChild ( screenEl ) ; stage = pixi . stage ; renderer = pixi . renderer as PIXI . Renderer ; pixi . ticker . stop ( ) ; charsContainer = new PIXI . Container ( ) ; bgContainer = new PIXI . Container ( ) ; cursorContainer = new PIXI . Container ( ) ; cursorSprite = new PIXI . Sprite ( ) ; cursorBg = new PIXI . Graphics ( ) ; stage . addChild ( bgContainer ) ; stage . addChild ( charsContainer ) ; stage . addChild ( cursorContainer ) ; cursorContainer . addChild ( cursorBg ) ; cursorContainer . addChild ( cursorSprite ) ; screenEl . style . width = `${ windowPixelSize ( ) . width }px` ; screenEl . style . height = `${ windowPixelSize ( ) . width }px` ; } ; const RETINA_SCALE = 2 ; const isRetina = ( ) => window . devicePixelRatio === RETINA_SCALE ; const scaledLetterSpacing = ( ) => { if ( isRetina ( ) || letterSpacing === 0 ) { return letterSpacing ; } return letterSpacing > 0 ? Math . floor ( letterSpacing / RETINA_SCALE ) : Math . ceil ( letterSpacing / RETINA_SCALE ) ; } ; const scaledFontSize = ( ) => fontSize * scale ; const measureCharSize = ( ) => { const char = document . createElement ( 'span' ) ; char . innerHTML = '0' ; char . style . fontFamily = fontFamily ; char . style . fontSize = `${ scaledFontSize ( ) }px` ; char . style . lineHeight = `${ Math . round ( scaledFontSize ( ) * lineHeight ) }px` ; char . style . position = 'absolute' ; char . style . left = '-1000px' ; char . style . top = '0' ; screenEl . appendChild ( char ) ; const oldCharWidth = charWidth ; const oldCharHeight = charHeight ; charWidth = Math . max ( char . offsetWidth + scaledLetterSpacing ( ) , 1 ) ; charHeight = char . offsetHeight ; if ( oldCharWidth !== charWidth || oldCharHeight !== charHeight ) { cursorSprite . x = - charWidth ; cursorEl . style . width = `${ charWidth }px` ; cursorEl . style . height = `${ charHeight }px` ; if ( charCanvas ) { charCanvas . width = charWidth * 3 ; charCanvas . height = charHeight ; } PIXI . utils . clearTextureCache ( ) ; } screenEl . removeChild ( char ) ; } ; const font = ( p ) => [ p . hiItalic ? 'italic' : '' , p . hiBold ? 'bold' : '' , `${ scaledFontSize ( ) }px` , fontFamily ] . join ( ' ' ) ; const getCharBitmap = ( char , props ) => { if ( props . hiUndercurl ) { charCtx . strokeStyle = props . spColor as string ; charCtx . lineWidth = scaledFontSize ( ) * 0.08 ; const x = charWidth ; const y = charHeight - ( scaledFontSize ( ) * 0.08 ) / 2 ; const h = charHeight * 0.2 ; charCtx . beginPath ( ) ; charCtx . moveTo ( x , y ) ; charCtx . bezierCurveTo ( x + x / 4 , y , x + x / 4 , y - h / 2 , x + x / 2 , y - h / 2 ) ; charCtx . bezierCurveTo ( x + ( x / 4 ) * 3 , y - h / 2 , x + ( x / 4 ) * 3 , y , x + x , y ) ; charCtx . stroke ( ) ; } charCtx . fillStyle = props . fgColor ; charCtx . font = font ( props ) ; charCtx . textAlign = 'left' ; charCtx . textBaseline = 'middle' ; if ( char ) { charCtx . fillText ( char , Math . round ( scaledLetterSpacing ( ) / 2 ) + charWidth , Math . round ( charHeight / 2 ) ) ; } if ( props . hiUnderline ) { charCtx . strokeStyle = props . fgColor ; charCtx . lineWidth = scale ; charCtx . beginPath ( ) ; charCtx . moveTo ( charWidth , charHeight - scale ) ; charCtx . lineTo ( charWidth * 2 , charHeight - scale ) ; charCtx . stroke ( ) ; } if ( props . hiStrikethrough ) { charCtx . strokeStyle = props . fgColor ; charCtx . lineWidth = scale ; charCtx . beginPath ( ) ; charCtx . moveTo ( charWidth , charHeight * 0.5 ) ; charCtx . lineTo ( charWidth * 2 , charHeight * 0.5 ) ; charCtx . stroke ( ) ; } return charCanvas . transferToImageBitmap ( ) ; } ; const getCharTexture = ( char , hlId ) => { const key = `${ char }:${ hlId }` ; if ( ! PIXI . utils . TextureCache [ key ] ) { const props = highlightTable [ hlId ] . calculated ; PIXI . Texture . addToCache ( PIXI . Texture . from ( getCharBitmap ( char , props ) ) , key ) ; } return PIXI . Texture . from ( key ) ; } ; const getBgTexture = ( bgColor , j ) => { const isLastCol = j === cols - 1 ; const key = `bg:${ bgColor }:${ isLastCol }` ; if ( ! PIXI . utils . TextureCache [ key ] ) { charCtx . fillStyle = bgColor ; if ( isLastCol ) { charCtx . fillRect ( 0 , 0 , charWidth * 2 , charHeight ) ; } else { charCtx . fillRect ( 0 , 0 , charWidth , charHeight ) ; } PIXI . Texture . addToCache ( PIXI . Texture . from ( charCanvas . transferToImageBitmap ( ) ) , key ) ; } return PIXI . Texture . from ( key ) ; } ; const initChar = ( i , j ) => { if ( ! chars [ i ] ) chars [ i ] = [ ] ; if ( ! chars [ i ] [ j ] ) { chars [ i ] [ j ] = { sprite : new PIXI . Sprite ( ) , bg : new PIXI . Sprite ( ) , } ; charsContainer . addChild ( chars [ i ] [ j ] . sprite ) ; bgContainer . addChild ( chars [ i ] [ j ] . bg ) ; } } ; const printChar = ( i , j , char , hlId ) => { initChar ( i , j ) ; chars [ i ] [ j ] . char = char ; chars [ i ] [ j ] . hlId = hlId ; chars [ i ] [ j ] . sprite . texture = getCharTexture ( char , hlId ) ; chars [ i ] [ j ] . sprite . position . set ( ( j - 1 ) * charWidth , i * charHeight ) ; chars [ i ] [ j ] . sprite . visible = true ; chars [ i ] [ j ] . bg . position . set ( j * charWidth , i * charHeight ) ; const bgColor = highlightTable [ hlId ] ?. calculated ?. bgColor ; if ( hlId !== 0 && bgColor && bgColor !== highlightTable [ 0 ] ?. calculated ?. bgColor ) { chars [ i ] [ j ] . bg . texture = getBgTexture ( bgColor , j ) ; chars [ i ] [ j ] . bg . visible = true ; } else { chars [ i ] [ j ] . bg . visible = false ; } } ; const cursorBlinkOn = ( ) => { cursorContainer . visible = true ; renderer . render ( stage ) ; } ; const cursorBlinkOff = ( ) => { cursorContainer . visible = false ; renderer . render ( stage ) ; } ; const cursorBlink = ( { blinkon , blinkoff , blinkwait , } = { } ) => { cursorContainer . visible = true ; if ( startCursorBlinkOnTimeout ) clearTimeout ( startCursorBlinkOnTimeout ) ; if ( startCursorBlinkOffTimeout ) clearTimeout ( startCursorBlinkOffTimeout ) ; if ( blinkOnCursorBlinkInterval ) clearInterval ( blinkOnCursorBlinkInterval ) ; if ( blinkOffCursorBlinkInterval ) clearInterval ( blinkOffCursorBlinkInterval ) ; startCursorBlinkOnTimeout = null ; startCursorBlinkOffTimeout = null ; blinkOnCursorBlinkInterval = null ; blinkOffCursorBlinkInterval = null ; if ( blinkoff && blinkon ) { startCursorBlinkOffTimeout = setTimeout ( ( ) => { cursorBlinkOff ( ) ; blinkOffCursorBlinkInterval = setInterval ( cursorBlinkOff , blinkoff + blinkon ) ; startCursorBlinkOnTimeout = setTimeout ( ( ) => { cursorBlinkOn ( ) ; blinkOnCursorBlinkInterval = setInterval ( cursorBlinkOn , blinkoff + blinkon ) ; } , blinkoff ) ; } , blinkwait ) ; } } ; const clearCursor = ( ) => { cursorBg . clear ( ) ; cursorSprite . visible = false ; } ; const redrawCursor = ( ) => { const m = modeInfoSet && modeInfoSet [ mode ] ; cursorBlink ( m ) ; if ( ! m ) return ; clearCursor ( ) ; const hlId = m . attr_id === 0 ? - 1 : m . attr_id ; cursorBg . beginFill ( getColorNum ( highlightTable [ hlId ] ?. calculated ?. bgColor ) ) ; if ( m . cursor_shape === 'block' ) { cursorChar = chars [ cursorPosition [ 0 ] ] [ cursorPosition [ 1 ] ] . char || ' ' ; cursorSprite . texture = getCharTexture ( cursorChar , hlId ) ; cursorBg . drawRect ( 0 , 0 , charWidth , charHeight ) ; cursorSprite . visible = true ; } else if ( m . cursor_shape === 'vertical' ) { const curWidth = m . cell_percentage ? Math . max ( scale , Math . round ( ( charWidth / 100 ) * m . cell_percentage ) ) : scale ; cursorBg . drawRect ( 0 , 0 , curWidth , charHeight ) ; } else if ( m . cursor_shape === 'horizontal' ) { const curHeight = m . cell_percentage ? Math . max ( scale , Math . round ( ( charHeight / 100 ) * m . cell_percentage ) ) : scale ; cursorBg . drawRect ( 0 , charHeight - curHeight , charWidth , curHeight ) ; } needRerender = true ; } ; const repositionCursor = ( newCursor ) : void => { if ( newCursor ) cursorPosition = newCursor ; const left = cursorPosition [ 1 ] * charWidth ; const top = cursorPosition [ 0 ] * charHeight ; cursorContainer . position . set ( left , top ) ; cursorEl . style . transform = `translate(${ left }px, ${ top }px)` ; redrawCursor ( ) ; } ; const optionSet = { guifont : ( newFont ) => { const [ newFontFamily , newFontSize ] = newFont . trim ( ) . split ( ':h' ) ; if ( newFontFamily && newFontFamily !== '' ) { nvim . command ( `VVset fontfamily=${ newFontFamily . replace ( /_/g , '\\ ' ) }` ) ; if ( newFontSize && newFontFamily !== '' ) { nvim . command ( `VVset fontsize=${ newFontSize }` ) ; } } } , } ; const reprintAllChars = ( ) => { if ( highlightTable [ 0 ] ?. calculated ?. bgColor ) { document . body . style . background = highlightTable [ 0 ] . calculated . bgColor ; transport . send ( 'set-background-color' , highlightTable [ 0 ] . calculated . bgColor ) ; } PIXI . utils . clearTextureCache ( ) ; for ( let i = 0 ; i <= rows ; i += 1 ) { for ( let j = 0 ; j <= cols ; j += 1 ) { initChar ( i , j ) ; const { char , hlId } = chars [ i ] [ j ] ; if ( char && isFinite ( hlId ) ) { printChar ( i , j , char , hlId as number ) ; } } } needRerender = true ; } ; const recalculateHighlightTable = ( ) => { ( ( Object . keys ( highlightTable ) as unknown ) as number [ ] ) . forEach ( ( id ) => { if ( id > 0 ) { const { foreground , background , special , reverse , standout , italic , bold , underline , undercurl , strikethrough , } = highlightTable [ id ] . value || { } ; const r = reverse || standout ; const fg = getColor ( foreground , highlightTable [ 0 ] ?. calculated ?. fgColor ) as string ; const bg = getColor ( background , highlightTable [ 0 ] ?. calculated ?. bgColor ) as string ; const sp = getColor ( special , highlightTable [ 0 ] ?. calculated ?. spColor ) as string ; highlightTable [ ( id as unknown ) as number ] . calculated = { fgColor : r ? bg : fg , bgColor : r ? fg : bg , spColor : sp , hiItalic : showItalic && ! ! italic , hiBold : showBold && ! ! bold , hiUnderline : showUnderline && ! ! underline , hiUndercurl : showUndercurl && ! ! undercurl , hiStrikethrough : showStrikethrough && ! ! strikethrough , } ; } } ) ; reprintAllChars ( ) ; } ; const rerender = throttle ( ( ) => { renderer . render ( stage ) ; } , 1000 / TARGET_FPS ) ; const rerenderIfNeeded = ( ) => { if ( needRerender ) { needRerender = false ; rerender ( ) ; } } ; const redrawCmd = { set_title : ( ) => { } , set_icon : ( ) => { } , win_viewport : ( ) => { } , mode_info_set : ( props ) => { modeInfoSet = props [ 0 ] [ 1 ] . reduce ( ( r , modeInfo ) => ( { ... r , [ modeInfo . name ] : modeInfo } ) , { } ) ; redrawCursor ( ) ; } , option_set : ( options ) => { options . forEach ( ( [ option , value ] ) => { if ( optionSet [ option ] ) { optionSet [ option ] ( value ) ; } else { } } ) ; } , mode_change : ( modes ) => { [ mode ] = modes [ modes . length - 1 ] ; redrawCursor ( ) ; } , mouse_on : ( ) => { } , mouse_off : ( ) => { } , busy_start : ( ) => { } , busy_stop : ( ) => { } , suspend : ( ) => { } , update_menu : ( ) => { } , bell : ( ) => { } , visual_bell : ( ) => { } , hl_group_set : ( ) => { } , flush : ( ) => { rerenderIfNeeded ( ) ; } , grid_resize : ( props ) => { cols = props [ 0 ] [ 1 ] ; rows = props [ 0 ] [ 2 ] ; if ( cols * charWidth > renderer . width || rows * charHeight > renderer . height ) { const width = ( cols + 1 ) * charWidth ; const height = rows * charHeight ; screenEl . style . width = `${ width }px` ; screenEl . style . height = `${ height }px` ; renderer . resize ( width , height ) ; needRerender = true ; } } , default_colors_set : ( props ) => { const [ foreground , background , special ] = props [ props . length - 1 ] ; const calculated = { bgColor : getColor ( background , defaultBgColor ) as string , fgColor : getColor ( foreground , defaultFgColor ) as string , spColor : getColor ( special , defaultSpColor ) , hiItalic : false , hiBold : false , hiUnderline : false , hiUndercurl : false , hiStrikethrough : false , } ; if ( ! highlightTable [ 0 ] || ! isEqual ( highlightTable [ 0 ] . calculated , calculated ) ) { highlightTable [ 0 ] = { calculated } ; highlightTable [ - 1 ] = { calculated : { ... calculated , bgColor : getColor ( foreground , defaultFgColor ) as string , fgColor : getColor ( background , defaultBgColor ) as string , } , } ; recalculateHighlightTable ( ) ; } } , hl_attr_define : ( props ) => { props . forEach ( ( [ id , value ] ) => { highlightTable [ id ] = { value , } ; } ) ; recalculateHighlightTable ( ) ; } , grid_line : ( props ) => { for ( let gridKey = 0 , gridLength = props . length ; gridKey < gridLength ; gridKey += 1 ) { const row = props [ gridKey ] [ 1 ] ; const col = props [ gridKey ] [ 2 ] ; const cells = props [ gridKey ] [ 3 ] ; let lineLength = 0 ; let currentHlId = 0 ; for ( let cellKey = 0 , cellsLength = cells . length ; cellKey < cellsLength ; cellKey += 1 ) { const [ char , hlId , length = 1 ] = cells [ cellKey ] ; if ( hlId !== undefined && isFinite ( hlId ) ) { currentHlId = hlId ; } for ( let j = 0 ; j < length ; j += 1 ) { printChar ( row , col + lineLength + j , char , currentHlId ) ; } lineLength += length ; } } needRerender = true ; if ( chars [ cursorPosition [ 0 ] ] && chars [ cursorPosition [ 0 ] ] [ cursorPosition [ 1 ] ] && cursorChar !== chars [ cursorPosition [ 0 ] ] [ cursorPosition [ 1 ] ] . char ) { redrawCursor ( ) ; } } , grid_clear : ( ) => { cursorPosition = [ 0 , 0 ] ; charsContainer . children . forEach ( ( c ) => { c . visible = false ; } ) ; bgContainer . children . forEach ( ( c ) => { c . visible = false ; } ) ; for ( let i = 0 ; i <= rows ; i += 1 ) { if ( ! chars [ i ] ) chars [ i ] = [ ] ; for ( let j = 0 ; j <= cols ; j += 1 ) { initChar ( i , j ) ; chars [ i ] [ j ] . char = null ; } } needRerender = true ; } , grid_destroy : ( ) => { } , grid_cursor_goto : ( [ [ _ , ... newCursor ] ] ) => { repositionCursor ( newCursor ) ; nvim . getMode ( ) ; } , grid_scroll : ( [ [ _grid , top , bottom , left , right , scrollCount ] ] ) => { for ( let i = scrollCount > 0 ? top : bottom - 1 ; scrollCount > 0 ? i <= bottom - scrollCount - 1 : i >= top - scrollCount ; i += scrollCount > 0 ? 1 : - 1 ) { for ( let j = left ; j <= right - 1 ; j += 1 ) { const sourceI = i + scrollCount ; initChar ( i , j ) ; initChar ( sourceI , j ) ; [ chars [ i ] [ j ] , chars [ sourceI ] [ j ] ] = [ chars [ sourceI ] [ j ] , chars [ i ] [ j ] ] ; if ( chars [ i ] [ j ] . sprite ) { chars [ i ] [ j ] . sprite . y = i * charHeight ; chars [ i ] [ j ] . bg . y = i * charHeight ; } if ( chars [ sourceI ] [ j ] . sprite ) { chars [ sourceI ] [ j ] . sprite . visible = false ; chars [ sourceI ] [ j ] . bg . visible = false ; chars [ sourceI ] [ j ] . sprite . y = sourceI * charHeight ; chars [ sourceI ] [ j ] . bg . y = sourceI * charHeight ; } } } needRerender = true ; } , } ; const handleSet = { fontfamily : ( newFontFamily ) => { fontFamily = `${ newFontFamily }, ${ DEFAULT_FONT_FAMILY }` ; } , fontsize : ( newFontSize ) => { fontSize = parseInt ( newFontSize , 10 ) ; } , letterspacing : ( newLetterSpacing ) => { letterSpacing = parseInt ( newLetterSpacing , 10 ) ; } , lineheight : ( newLineHeight ) => { lineHeight = parseFloat ( newLineHeight ) ; } , bold : ( value ) => { showBold = value ; } , italic : ( value ) => { showItalic = value ; } , underline : ( value ) => { showUnderline = value ; } , undercurl : ( value ) => { showUndercurl = value ; } , strikethrough : ( value ) => { showStrikethrough = value ; } , } ; const redraw = ( args ) => { args . forEach ( ( [ cmd , ... props ] ) => { const command = redrawCmd [ cmd ] ; if ( command ) { command ( props ) ; } else { console . warn ( 'Unknown redraw command' , cmd , props ) ; } } ) ; } ; const setScale = ( ) => { scale = isRetina ( ) ? RETINA_SCALE : 1 ; screenContainer . style . transform = `scale(${ 1 / scale })` ; screenContainer . style . width = `${ scale * 100 }%` ; screenContainer . style . height = `${ scale * 100 }%` ; window . matchMedia ( 'screen and (min-resolution: 2dppx)' ) . addListener ( async ( ) => { setScale ( ) ; measureCharSize ( ) ; await nvim . uiTryResize ( cols , rows ) ; } ) ; } ; /**
     * Return grid [col, row] coordinates by pixel coordinates.
     */ const screenCoords = ( width , height ) : [ number , number ] => { return [ Math . floor ( ( width * scale ) / charWidth ) , Math . floor ( ( height * scale ) / charHeight ) ] ; } ; const resize = ( forceRedraw = false ) => { const [ newCols , newRows ] = screenCoords ( window . innerWidth , window . innerHeight ) ; if ( newCols !== cols || newRows !== rows || forceRedraw ) { cols = newCols ; rows = newRows ; nvim . uiTryResize ( cols , rows ) ; } } ; const uiAttach = ( ) => { [ cols , rows ] = screenCoords ( window . innerWidth , window . innerHeight ) ; nvim . uiAttach ( cols , rows , { ext_linegrid : true } ) ; window . addEventListener ( 'resize' , throttle ( ( ) => resize ( ) , 1000 / TARGET_FPS ) ) ; } ; const updateSettings = ( newSettings , isInitial = false ) => { let requireRedraw = isInitial ; let requireRecalculateHighlight = false ; const requireRedrawProps = [ 'fontfamily' , 'fontsize' , 'letterspacing' , 'lineheight' , 'bold' , 'italic' , 'underline' , 'undercurl' , 'strikethrough' , ] ; const requireRecalculateHighlightProps = [ 'bold' , 'italic' , 'underline' , 'undercurl' , 'strikethrough' , ] ; Object . keys ( newSettings ) . forEach ( ( key ) => { if ( handleSet [ key ] ) { requireRedraw = requireRedraw || requireRedrawProps . includes ( key ) ; requireRecalculateHighlight = requireRecalculateHighlight || requireRecalculateHighlightProps . includes ( key ) ; handleSet [ key ] ( newSettings [ key ] ) ; } } ) ; if ( requireRecalculateHighlight && ! isInitial ) { recalculateHighlightTable ( ) ; } if ( requireRedraw ) { measureCharSize ( ) ; PIXI . utils . clearTextureCache ( ) ; if ( ! isInitial ) { resize ( true ) ; } } } ; initScreen ( ) ; initCursor ( ) ; setScale ( ) ; nvim . on ( 'redraw' , redraw ) ; transport . on ( 'updateSettings' , ( s ) => updateSettings ( s ) ) ; updateSettings ( settings , true ) ; uiAttach ( ) ; return { screenCoords , getCursorElement , } ; } ; export default screen ;