/**
 * Taken from Code-surfer/step-parser!
 */ import flat from 'array.prototype.flat' ; import { forEachObjIndexed , isArray , maxBy , min , minBy , omit , omitBy , pipeVal , sortNumberArray , startsWith , symmetricDifference , times , toString , } from '.' ; type LineIndex = number ; type ColumnIndex = number ; export type ParsedFocusLine = true | number [ ] ; export type ParsedFocus = Record < number , ParsedFocusLine > ; export function fromEntries < K extends string | number | symbol , V > ( pairs ) { const result = { } as Record < K , V > ; let index = - 1 , length = pairs == null ? 0 : pairs . length ; while ( ++ index < length ) { const pair = pairs [ index ] ; result [ pair [ 0 ] ] = pair [ 1 ] ; } return result ; } export function deserializeFocus ( focus ) { if ( ! focus ) { throw new Error ( 'Focus cannot be empty' ) ; } const parts = focus . split ( /,(?![^[]*\])/g ) . map ( parsePart ) ; return fromEntries ( flat ( parts ) ) ; } type Part = [ LineIndex , true | ColumnIndex [ ] ] ; function parsePart ( part ) { const columnsMatch = part . match ( /(\d+)\[(.+)\]/ ) ; if ( columnsMatch ) { const [ , line , columns ] = columnsMatch ; const columnsList = columns . split ( ',' ) . map ( expandString ) ; const lineIndex = Number ( line ) - 1 ; const columnIndexes = flat ( columnsList ) . map ( ( c ) => c - 1 ) ; return [ [ lineIndex , columnIndexes ] ] ; } return expandString ( part ) . map ( ( lineNumber ) => [ lineNumber - 1 , true ] ) ; } function expandString ( part ) { const [ start , end ] = part . split ( ':' ) ; if ( ! isNaturalNumber ( start ) ) { throw new FocusNumberError ( start ) ; } const startNumber = Number ( start ) ; if ( startNumber < 1 ) { throw new LineOrColumnNumberError ( ) ; } if ( ! end ) { return [ startNumber ] ; } if ( ! isNaturalNumber ( end ) ) { throw new FocusNumberError ( end ) ; } const list = [ ] ; for ( let i = startNumber ; i <= + end ; i ++ ) { list . push ( i ) ; } return list ; } function isNaturalNumber ( n ) { n = n . toString ( ) ; const n1 = Math . abs ( n ) , n2 = parseInt ( n , 10 ) ; return ! isNaN ( n1 ) && n2 === n1 && n1 . toString ( ) === n ; } export function getFocusSize ( focus ) { const lineIndexList = Object . keys ( focus ) . map ( ( k ) => + k ) ; const focusStart = Math . min . apply ( Math , lineIndexList ) ; const focusEnd = Math . max . apply ( Math , lineIndexList ) ; return { focusCenter : ( focusStart + focusEnd + 1 ) / 2 , focusCount : focusEnd - focusStart + 1 , } ; } export class LineOrColumnNumberError extends Error { constructor ( ) { super ( `Invalid line or column number in focus string` ) ; Object . setPrototypeOf ( this , new . target . prototype ) ; } } export class FocusNumberError extends Error { number : string ; constructor ( number ) { super ( `Invalid number "${ number }" in focus string` ) ; this . number = number ; Object . setPrototypeOf ( this , new . target . prototype ) ; } } /**
 * Custom code below!
 */ export function serializeFocus ( parsedFocus ) { let focusString = '' ; forEachObjIndexed ( ( val , lineNumber ) => { const serializedLineNumber = Number ( lineNumber ) + 1 ; if ( val === true ) { focusString = `${ focusString },${ serializedLineNumber }` ; } if ( isArray ( val ) ) { focusString = `${ focusString },${ serializedLineNumber }[${ val . map ( ( v ) => Number ( v ) + 1 ) . join ( ',' ) }]` ; } } , parsedFocus ) ; return startsWith ( ',' , focusString ) ? focusString . replace ( ',' , '' ) : focusString ; } export const removeLineFromFocus = ( lineNumber , parsedFocus ) => omit ( [ toString ( lineNumber ) ] , parsedFocus ) ; export const addLineToFocus = ( lineNumber , parsedFocus ) : ParsedFocus => ( { ... parsedFocus , [ lineNumber ] : true } ) ; type CodeMirrorPositionWithContent = CodeMirror . Position & { content : string ; } ; export const addFocusToSelection = ( { anchor , head , parsedFocus , } ) : ParsedFocus => { const startingLine = minBy < CodeMirrorPositionWithContent > ( ( x ) => x . line + x . ch , anchor , head ) ; const endingLine = maxBy < CodeMirrorPositionWithContent > ( ( x ) => x . line + x . ch , anchor , head ) ; const startingLineNumber = startingLine . line ; const endingLineNumber = endingLine . line ; const numberOfLinesSelectionSpans = Math . abs ( startingLineNumber - endingLineNumber ) ; let newParsedFocus = parsedFocus ; if ( numberOfLinesSelectionSpans === 0 ) { newParsedFocus = { ... parsedFocus , [ startingLineNumber ] : computeNewFocusForLine ( parsedFocus [ startingLineNumber ] , createContiguousArray ( startingLine . ch ) ( endingLine . ch ) ) , } ; } if ( numberOfLinesSelectionSpans === 1 ) { newParsedFocus = { ... parsedFocus , [ startingLineNumber ] : computeNewFocusForLine ( parsedFocus [ startingLineNumber ] , createContiguousArray ( startingLine . ch ) ( startingLine . content . length ) ) , [ endingLineNumber ] : computeNewFocusForLine ( parsedFocus [ endingLineNumber ] , createContiguousArray ( 0 ) ( endingLine . ch ) ) , } ; } const createFocusedLinesObject = ( ) => { const linesObject = { } ; for ( let index = startingLineNumber + 1 ; index <= endingLineNumber - 1 ; index ++ ) { linesObject [ index ] = true ; } return linesObject ; } ; if ( numberOfLinesSelectionSpans > 1 ) { newParsedFocus = { ... parsedFocus , [ startingLineNumber ] : computeNewFocusForLine ( parsedFocus [ startingLineNumber ] , createContiguousArray ( startingLine . ch ) ( startingLine . content . length ) ) , ... createFocusedLinesObject ( ) , [ endingLineNumber ] : computeNewFocusForLine ( parsedFocus [ endingLineNumber ] , createContiguousArray ( 0 ) ( endingLine . ch ) ) , } ; } return omitBy < ParsedFocusLine , ParsedFocus > ( ( val ) => isArray ( val ) && val . length === 0 , newParsedFocus ) ; } ; const createContiguousArray = ( x1 ) => ( x2 ) => { const startCharacter = min ( x1 ) ( x2 ) ; return times ( ( i ) => startCharacter + i , Math . abs ( x1 - x2 ) ) ; } ; /**
 * Takes in a line and returns the symmetric difference if the user already has a selection for a given
 * line. Otherwise it returns the new data.
 */ const computeNewFocusForLine = ( line , newArraySelection ) => { if ( isArray ( line ) ) { return pipeVal ( line , symmetricDifference ( newArraySelection ) , sortNumberArray ) ; } return newArraySelection ; } ;