import { insertNode , fixInsert } from "../tree/insert" ; import { Color , PageContent , RedBlackTree } from "../pageModel" ; import { SENTINEL_INDEX , EMPTY_TREE_ROOT , NodePosition , prevNode , } from "../tree/tree" ; import { Buffer , ContentNode } from "./contentModel" ; import { findNodeAtOffset , getLineStarts , getNodeContent , NodePositionOffset , updateContentTreeMetadata , getContentBetweenOffsets , } from "./tree" ; import { TagType , StructureNode } from "../structureTree/structureModel" ; import { hasStartTag } from "../compiler/parser" ; import { updateNodePairTag } from "../structureTree/tree" ; /**
 * The desired content and offset for an insertion operation.
 */ export interface ContentInsert { content : string ; /**
     * Offset of the content to insert in terms of the entire document.
     */ globalOffset : number ; } param content param page param indexToInsertAfter function createNodeCreateBuffer ( content , page , indexToInsertAfter ? ) { const newBuffer = { content : content . content , isReadOnly : false , lineStarts : getLineStarts ( content . content ) , } ; const newNode = { bufferIndex : page . buffers . length , color : Color . Red , end : { column : newBuffer . content . length - newBuffer . lineStarts [ newBuffer . lineStarts . length - 1 ] , line : newBuffer . lineStarts . length - 1 , } , left : SENTINEL_INDEX , leftCharCount : 0 , leftLineFeedCount : 0 , length : content . content . length , lineFeedCount : newBuffer . lineStarts . length - 1 , parent : SENTINEL_INDEX , right : SENTINEL_INDEX , start : { column : 0 , line : 0 } , } ; page . previouslyInsertedContentNodeIndex = page . content . nodes . length ; page . previouslyInsertedContentNodeOffset = content . globalOffset ; page . buffers . push ( newBuffer ) ; insertNode ( page . content , newNode , content . globalOffset , indexToInsertAfter ) ; } param content param page param indexToInsertAfter function createNodeAppendToBuffer ( content , page , indexToInsertAfter ? ) { const oldBuffer = page . buffers [ page . buffers . length - 1 ] ; const newContent = oldBuffer . content + content . content ; const updatedBuffer = { content : newContent , isReadOnly : oldBuffer . isReadOnly , lineStarts : getLineStarts ( newContent ) , } ; const newNode = { bufferIndex : page . buffers . length - 1 , color : Color . Red , end : { column : updatedBuffer . content . length - updatedBuffer . lineStarts [ updatedBuffer . lineStarts . length - 1 ] , line : updatedBuffer . lineStarts . length - 1 , } , left : SENTINEL_INDEX , leftCharCount : 0 , leftLineFeedCount : 0 , length : content . content . length , lineFeedCount : updatedBuffer . lineStarts . length - oldBuffer . lineStarts . length , parent : SENTINEL_INDEX , right : SENTINEL_INDEX , start : { column : oldBuffer . content . length - oldBuffer . lineStarts [ oldBuffer . lineStarts . length - 1 ] , line : oldBuffer . lineStarts . length - 1 , } , } ; page . buffers [ page . buffers . length - 1 ] = updatedBuffer ; page . previouslyInsertedContentNodeIndex = page . content . nodes . length ; page . previouslyInsertedContentNodeOffset = content . globalOffset ; insertNode ( page . content , newNode , content . globalOffset , indexToInsertAfter ) ; } param content param page param maxBufferLength function insertAtNodeExtremity ( content , page , maxBufferLength , nodePosition ? ) { let indexToInsertAfter ; if ( nodePosition ) { indexToInsertAfter = prevNode ( page . content . nodes , nodePosition . index ) . index ; if ( indexToInsertAfter === 0 ) { indexToInsertAfter = undefined ; } } if ( content . content . length + page . buffers [ page . buffers . length - 1 ] . content . length <= maxBufferLength && page . buffers [ page . buffers . length - 1 ] . isReadOnly === false ) { createNodeAppendToBuffer ( content , page ) ; } else { createNodeCreateBuffer ( content , page ) ; } } param content param page param maxBufferLength param nodePosition function insertInsideNode ( content , page , maxBufferLength , { node , nodeIndex , remainder } ) { const nodeContent = getNodeContent ( page , nodeIndex ) ; const firstPartContent = nodeContent . slice ( 0 , remainder ) ; const firstPartLineStarts = getLineStarts ( firstPartContent ) ; const firstPartNode = { ... node , end : { column : firstPartContent . length - firstPartLineStarts [ firstPartLineStarts . length - 1 ] + node . start . column , line : firstPartLineStarts . length - 1 + node . start . line , } , length : firstPartContent . length , lineFeedCount : firstPartLineStarts . length - 1 , } ; page . content . nodes [ nodeIndex ] = firstPartNode ; const secondPartNode = { bufferIndex : node . bufferIndex , color : Color . Red , end : node . end , left : SENTINEL_INDEX , leftCharCount : 0 , leftLineFeedCount : 0 , length : node . length - firstPartNode . length , lineFeedCount : node . lineFeedCount - firstPartNode . lineFeedCount , parent : SENTINEL_INDEX , right : SENTINEL_INDEX , start : firstPartNode . end , } ; updateContentTreeMetadata ( page . content , nodeIndex , - secondPartNode . length , - secondPartNode . lineFeedCount ) ; insertNode ( page . content , secondPartNode , content . globalOffset ) ; fixInsert ( page . content , page . content . nodes . length - 1 ) ; insertAtNodeExtremity ( content , page , maxBufferLength ) ; page . previouslyInsertedContentNodeIndex = page . content . nodes . length - 1 ; page . previouslyInsertedContentNodeOffset = content . globalOffset ; } param content param page param maxBufferLength function insertAtEndPreviouslyInsertedNode ( content , page , maxBufferLength ) { if ( content . content . length + page . buffers [ page . buffers . length - 1 ] . content . length <= maxBufferLength ) { const oldBuffer = page . buffers [ page . buffers . length - 1 ] ; const newContent = oldBuffer . content + content . content ; const buffer = { content : newContent , isReadOnly : oldBuffer . isReadOnly , lineStarts : getLineStarts ( newContent ) , } ; const node = page . content . nodes [ page . content . nodes . length - 1 ] ; node . end = { column : buffer . content . length - buffer . lineStarts [ buffer . lineStarts . length - 1 ] , line : buffer . lineStarts . length - 1 , } ; node . lineFeedCount = buffer . lineStarts . length - 1 ; node . length += content . content . length ; page . buffers [ page . buffers . length - 1 ] = buffer ; } else { createNodeCreateBuffer ( content , page ) ; } } function convertBreakToParagraph ( tree , contentOffset , structureNodeIndex ) { const startNode = tree . nodes [ structureNodeIndex ] ; startNode . style = { marginBottom : "0pt" , marginTop : "0pt" , } ; startNode . tag = "p" ; startNode . tagType = TagType . StartTag ; tree . nodes [ structureNodeIndex ] = startNode ; const endNode = { color : Color . Red , id : tree . nodes [ structureNodeIndex ] . id , left : SENTINEL_INDEX , leftSubTreeLength : 0 , length : 0 , parent : SENTINEL_INDEX , right : SENTINEL_INDEX , tag : "p" , tagType : TagType . EndTag , } ; insertNode ( tree , endNode , contentOffset , structureNodeIndex ) ; fixInsert ( tree , tree . nodes . length - 1 ) ; } param content param page param structureNodeIndex param maxBufferLength param structureNodeOffset export function insertContent ( page , content , structureNodeIndex , maxBufferLength , structureNodeOffset ? ) { const structureNode = page . structure . nodes [ structureNodeIndex ] ; if ( structureNode . tag === "br" ) { convertBreakToParagraph ( page . structure , content . globalOffset , structureNodeIndex ) ; } if ( page . content . root === EMPTY_TREE_ROOT ) { createNodeCreateBuffer ( content , page ) ; return ; } let previouslyInsertedNode ; if ( page . previouslyInsertedContentNodeIndex !== null && page . previouslyInsertedContentNodeOffset !== null ) { previouslyInsertedNode = page . content . nodes [ page . previouslyInsertedContentNodeIndex ] ; } if ( previouslyInsertedNode !== undefined && content . globalOffset === page . previouslyInsertedContentNodeOffset ! + previouslyInsertedNode . length ) { insertAtEndPreviouslyInsertedNode ( content , page , maxBufferLength ) ; } else { const nodePosition = findNodeAtOffset ( page . content , content . globalOffset ) ; if ( nodePosition . nodeLocalOffset < content . globalOffset && content . globalOffset < nodePosition . nodeLocalOffset + nodePosition . node . length ) { insertInsideNode ( content , page , maxBufferLength , nodePosition ) ; } else { insertAtNodeExtremity ( content , page , maxBufferLength ) ; } } if ( page ) { fixInsert ( page . content , page . content . nodes . length - 1 ) ; } if ( structureNodeIndex !== SENTINEL_INDEX ) { structureNode . length += content . content . length ; } if ( structureNodeOffset ) { const hasStart = hasStartTag ( getContentBetweenOffsets ( page , structureNodeOffset , structureNodeOffset + structureNode . length ) . content ) ; if ( hasStart ) { updateNodePairTag ( page , structureNodeIndex , hasStart . tag ) ; } } }