From 2c3efff99bee0f6673ab9faaf3552cb034c410a9 Mon Sep 17 00:00:00 2001 From: Ran Luo Date: Sat, 23 Nov 2024 11:48:58 +0800 Subject: [PATCH] refactor: refactor doc view model cache (#4130) --- examples/src/docs/main.ts | 4 +- .../src/commands/commands/table/table.ts | 14 +- .../src/services/selection/selection-utils.ts | 2 +- .../layout/block/paragraph/line-adjustment.ts | 2 +- .../layout/block/paragraph/linebreaking.ts | 2 +- .../docs/layout/block/paragraph/shaping.ts | 2 +- .../src/components/docs/layout/block/table.ts | 4 +- .../components/docs/layout/doc-skeleton.ts | 12 +- .../src/components/docs/layout/model/page.ts | 3 +- .../src/components/docs/layout/tools.ts | 4 +- .../docs/view-model/document-view-model.ts | 523 ++++-------------- 11 files changed, 136 insertions(+), 436 deletions(-) diff --git a/examples/src/docs/main.ts b/examples/src/docs/main.ts index 07d69e555bc..0f36562fb29 100644 --- a/examples/src/docs/main.ts +++ b/examples/src/docs/main.ts @@ -25,7 +25,7 @@ import { UniverDocsThreadCommentUIPlugin } from '@univerjs/docs-thread-comment-u import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; -import { DEFAULT_DOCUMENT_DATA_SIMPLE } from '@univerjs/mockdata'; +import { DEFAULT_DOCUMENT_DATA_CN } from '@univerjs/mockdata'; import { UniverUIPlugin } from '@univerjs/ui'; import { enUS, faIR, ruRU, zhCN } from '../locales'; @@ -69,7 +69,7 @@ univer.registerPlugin(UniverDocsHyperLinkUIPlugin); univer.registerPlugin(UniverDocsMentionUIPlugin); if (!IS_E2E) { - univer.createUnit(UniverInstanceType.UNIVER_DOC, DEFAULT_DOCUMENT_DATA_SIMPLE); + univer.createUnit(UniverInstanceType.UNIVER_DOC, DEFAULT_DOCUMENT_DATA_CN); } // use for console test diff --git a/packages/docs-ui/src/commands/commands/table/table.ts b/packages/docs-ui/src/commands/commands/table/table.ts index a102a957f15..679ce344aa4 100644 --- a/packages/docs-ui/src/commands/commands/table/table.ts +++ b/packages/docs-ui/src/commands/commands/table/table.ts @@ -276,7 +276,7 @@ export function getInsertRowActionsParams(rangeInfo: IRangeInfo, position: INSER let rowIndex = 0; // TODO: handle nested tables - for (const section of vm.children) { + for (const section of vm.getChildren()) { for (const paragraph of section.children) { const { children } = paragraph; const table = children[0]; @@ -324,7 +324,7 @@ export function getInsertColumnActionsParams(rangeInfo: IRangeInfo, position: IN let table: Nullable = null; let columnIndex = -1; - for (const section of vm.children) { + for (const section of vm.getChildren()) { for (const paragraph of section.children) { const { children } = paragraph; const tableNode = children[0]; @@ -418,7 +418,7 @@ export function getDeleteRowsActionsParams(rangeInfo: IRangeInfo, viewModel: Doc let cursor = -1; let selectWholeTable = false; - for (const section of vm.children) { + for (const section of vm.getChildren()) { for (const paragraph of section.children) { const { children } = paragraph; const table = children[0]; @@ -494,7 +494,7 @@ export function getDeleteColumnsActionParams(rangeInfo: IRangeInfo, viewModel: D let startColumnIndex = -1; let endColumnIndex = -1; - for (const section of vm.children) { + for (const section of vm.getChildren()) { for (const paragraph of section.children) { const { children } = paragraph; const tableNode = children[0]; @@ -573,7 +573,7 @@ export function getDeleteTableActionParams(rangeInfo: IRangeInfo, viewModel: Doc let len = 0; let cursor = -1; - for (const section of vm.children) { + for (const section of vm.getChildren()) { for (const paragraph of section.children) { const { children } = paragraph; const table = children[0]; @@ -624,7 +624,7 @@ export function getDeleteRowContentActionParams(rangeInfo: IRangeInfo, viewModel let startColumnIndex = -1; let endColumnIndex = -1; - for (const section of vm.children) { + for (const section of vm.getChildren()) { for (const paragraph of section.children) { const { children } = paragraph; const tableNode = children[0]; @@ -706,7 +706,7 @@ export function getCellOffsets(viewModel: DocumentViewModel, range: ITextRangeWi let targetTable = null; - for (const section of viewModel.children) { + for (const section of viewModel.getChildren()) { for (const paragraph of section.children) { const table = paragraph.children[0]; if (table) { diff --git a/packages/docs-ui/src/services/selection/selection-utils.ts b/packages/docs-ui/src/services/selection/selection-utils.ts index 9281c679eaf..137a6d36197 100644 --- a/packages/docs-ui/src/services/selection/selection-utils.ts +++ b/packages/docs-ui/src/services/selection/selection-utils.ts @@ -194,7 +194,7 @@ export function getRangeListFromSelection( let end = endOffset; // TODO: @JOCS handle in header and footer. - for (const section of viewModel.children) { + for (const section of viewModel.getChildren()) { for (const paragraph of section.children) { const { startIndex, endIndex, children } = paragraph; const table = children[0]; diff --git a/packages/engine-render/src/components/docs/layout/block/paragraph/line-adjustment.ts b/packages/engine-render/src/components/docs/layout/block/paragraph/line-adjustment.ts index e6565dfd629..8e2e246a263 100644 --- a/packages/engine-render/src/components/docs/layout/block/paragraph/line-adjustment.ts +++ b/packages/engine-render/src/components/docs/layout/block/paragraph/line-adjustment.ts @@ -276,7 +276,7 @@ export function lineAdjustment( sectionBreakConfig: ISectionBreakConfig ) { const { endIndex } = paragraphNode; - const paragraph = viewModel.getParagraph(endIndex, true) || { startIndex: 0 }; + const paragraph = viewModel.getParagraph(endIndex) || { startIndex: 0 }; lineIterator(pages, (line) => { // Only need to adjust the current paragraph. diff --git a/packages/engine-render/src/components/docs/layout/block/paragraph/linebreaking.ts b/packages/engine-render/src/components/docs/layout/block/paragraph/linebreaking.ts index 75278427ffc..3e915e0318e 100644 --- a/packages/engine-render/src/components/docs/layout/block/paragraph/linebreaking.ts +++ b/packages/engine-render/src/components/docs/layout/block/paragraph/linebreaking.ts @@ -146,7 +146,7 @@ export function lineBreaking( const { endIndex, blocks = [], children } = paragraphNode; const { segmentId } = curPage; - const paragraph = viewModel.getParagraph(endIndex, true) || { startIndex: 0 }; + const paragraph = viewModel.getParagraph(endIndex) || { startIndex: 0 }; const { paragraphStyle = {}, bullet } = paragraph; diff --git a/packages/engine-render/src/components/docs/layout/block/paragraph/shaping.ts b/packages/engine-render/src/components/docs/layout/block/paragraph/shaping.ts index 8121052a5b4..0bf9158ebbe 100644 --- a/packages/engine-render/src/components/docs/layout/block/paragraph/shaping.ts +++ b/packages/engine-render/src/components/docs/layout/block/paragraph/shaping.ts @@ -123,7 +123,7 @@ export function shaping( const shapedTextList: IShapedText[] = []; let breaker = new LineBreaker(content); const { endIndex } = paragraphNode; - const paragraph = viewModel.getParagraph(endIndex, true) || { startIndex: 0 }; + const paragraph = viewModel.getParagraph(endIndex) || { startIndex: 0 }; const { paragraphStyle = {} } = paragraph; const { snapToGrid = BooleanNumber.TRUE } = paragraphStyle; let last = 0; diff --git a/packages/engine-render/src/components/docs/layout/block/table.ts b/packages/engine-render/src/components/docs/layout/block/table.ts index a15003f895b..3dedd8ca3c8 100644 --- a/packages/engine-render/src/components/docs/layout/block/table.ts +++ b/packages/engine-render/src/components/docs/layout/block/table.ts @@ -30,7 +30,7 @@ export function createTableSkeleton( sectionBreakConfig: ISectionBreakConfig ): IDocumentSkeletonTable { const { startIndex, endIndex, children: rowNodes } = tableNode; - const table = viewModel.getTable(startIndex); + const table = viewModel.getTableByStartIndex(startIndex)?.tableSource; if (table == null) { throw new Error('Table not found'); } @@ -166,7 +166,7 @@ export function createTableSkeletons( const pageContentHeight = pageHeight - marginTop - marginBottom; const { startIndex, endIndex, children: rowNodes } = tableNode; - const table = viewModel.getTable(startIndex); + const table = viewModel.getTableByStartIndex(startIndex)?.tableSource; if (table == null) { throw new Error('Table not found when creating table skeletons'); } diff --git a/packages/engine-render/src/components/docs/layout/doc-skeleton.ts b/packages/engine-render/src/components/docs/layout/doc-skeleton.ts index 801d2c821e0..7f462112bb6 100644 --- a/packages/engine-render/src/components/docs/layout/doc-skeleton.ts +++ b/packages/engine-render/src/components/docs/layout/doc-skeleton.ts @@ -1001,7 +1001,7 @@ export class DocumentSkeleton extends Skeleton { private _prepareLayoutContext(): ILayoutContext { const viewModel = this.getViewModel(); const dataModel = viewModel.getDataModel(); - const { headerTreeMap, footerTreeMap } = viewModel; + const { headerTreeMap, footerTreeMap } = viewModel.getHeaderFooterTreeMap(); const { documentStyle, drawings, lists: customLists = {} } = dataModel; const lists = { ...PRESET_LIST_TYPE, @@ -1089,8 +1089,6 @@ export class DocumentSkeleton extends Skeleton { const allSkeletonPages = skeleton.pages; - viewModel.resetCache(); - let startSectionIndex = 0; const layoutAnchor = ctx.layoutStartPointer['']; @@ -1099,8 +1097,8 @@ export class DocumentSkeleton extends Skeleton { ctx.layoutStartPointer[''] = null; if (layoutAnchor != null) { - for (let sectionIndex = 0; sectionIndex < viewModel.children.length; sectionIndex++) { - const sectionNode = viewModel.children[sectionIndex]; + for (let sectionIndex = 0; sectionIndex < viewModel.getChildren().length; sectionIndex++) { + const sectionNode = viewModel.getChildren()[sectionIndex]; const { endIndex, startIndex } = sectionNode; if (layoutAnchor >= startIndex && layoutAnchor <= endIndex) { startSectionIndex = sectionIndex; @@ -1110,8 +1108,8 @@ export class DocumentSkeleton extends Skeleton { } // Loop the sections with the start section index. - for (let i = startSectionIndex, len = viewModel.children.length; i < len; i++) { - const sectionNode = viewModel.children[i]; + for (let i = startSectionIndex, len = viewModel.getChildren().length; i < len; i++) { + const sectionNode = viewModel.getChildren()[i]; const sectionBreakConfig = prepareSectionBreakConfig(ctx, i); const { sectionType, columnProperties, columnSeparatorType, sectionTypeNext, pageNumberStart = 1 } = sectionBreakConfig; diff --git a/packages/engine-render/src/components/docs/layout/model/page.ts b/packages/engine-render/src/components/docs/layout/model/page.ts index f421812e1c7..5059d8ec915 100644 --- a/packages/engine-render/src/components/docs/layout/model/page.ts +++ b/packages/engine-render/src/components/docs/layout/model/page.ts @@ -239,7 +239,7 @@ function _createSkeletonHeaderFooter( const page = dealWithSection( ctx, headerOrFooterViewModel, - headerOrFooterViewModel.children[0], + headerOrFooterViewModel.getChildren()[0], areaPage, headerFooterConfig, layoutAnchor @@ -248,7 +248,6 @@ function _createSkeletonHeaderFooter( if (ctx.isDirty && count < 10) { count++; resetContext(ctx); - headerOrFooterViewModel.resetCache(); return _createSkeletonHeaderFooter( ctx, diff --git a/packages/engine-render/src/components/docs/layout/tools.ts b/packages/engine-render/src/components/docs/layout/tools.ts index 8e2e91dbcac..9a9cacfac16 100644 --- a/packages/engine-render/src/components/docs/layout/tools.ts +++ b/packages/engine-render/src/components/docs/layout/tools.ts @@ -1004,7 +1004,7 @@ const DEFAULT_MODERN_SECTION_BREAK: Partial = { export function prepareSectionBreakConfig(ctx: ILayoutContext, nodeIndex: number) { const { viewModel, dataModel, docsConfig } = ctx; - const sectionNode = viewModel.children[nodeIndex]; + const sectionNode = viewModel.getChildren()[nodeIndex]; let { documentStyle } = dataModel; const { documentFlavor } = documentStyle; let sectionBreak = viewModel.getSectionBreak(sectionNode.endIndex) || DEFAULT_SECTION_BREAK; @@ -1081,7 +1081,7 @@ export function prepareSectionBreakConfig(ctx: ILayoutContext, nodeIndex: number renderConfig = global_renderConfig, } = sectionBreak; - const sectionNodeNext = viewModel.children[nodeIndex + 1]; + const sectionNodeNext = viewModel.getChildren()[nodeIndex + 1]; const sectionTypeNext = viewModel.getSectionBreak(sectionNodeNext?.endIndex)?.sectionType; const headerIds = { defaultHeaderId, evenPageHeaderId, firstPageHeaderId }; diff --git a/packages/engine-render/src/components/docs/view-model/document-view-model.ts b/packages/engine-render/src/components/docs/view-model/document-view-model.ts index 296fc296ee0..780c22ddd4b 100644 --- a/packages/engine-render/src/components/docs/view-model/document-view-model.ts +++ b/packages/engine-render/src/components/docs/view-model/document-view-model.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import type { ICustomDecorationForInterceptor, ICustomRangeForInterceptor, ICustomTable, IDisposable, IDocumentBody, ITextRun, Nullable } from '@univerjs/core'; -import { DataStreamTreeNodeType, DataStreamTreeTokenType, DocumentDataModel, toDisposable } from '@univerjs/core'; +import type { DocumentDataModel, ICustomBlock, ICustomDecorationForInterceptor, ICustomRangeForInterceptor, ICustomTable, IDisposable, IParagraph, ISectionBreak, ITable, ITextRun, Nullable } from '@univerjs/core'; +import { DataStreamTreeNodeType, DataStreamTreeTokenType, toDisposable } from '@univerjs/core'; import { BehaviorSubject } from 'rxjs'; import { DataStreamTreeNode } from './data-stream-tree-node'; @@ -169,28 +169,37 @@ export function parseDataStreamToTree(dataStream: string, tables?: ICustomTable[ return { sectionList, tableNodeCache }; } +interface ITableCoupleCache { + table: ICustomTable; + tableSource: ITable; +} + export class DocumentViewModel implements IDisposable { + private _interceptor: Nullable = null; + private _cacheSize = 1000; private _textRunsCache: Map> = new Map(); - private _interceptor: Nullable = null; + private _paragraphCache: Map = new Map(); + + private _sectionBreakCache: Map = new Map(); + + private _customBlockCache: Map = new Map(); + + private _tableCache: Map = new Map(); private _tableNodeCache: Map = new Map(); - children: DataStreamTreeNode[] = []; - private _sectionBreakCurrentIndex = 0; - private _paragraphCurrentIndex = 0; - private _textRunCurrentIndex = 0; - private _customBlockCurrentIndex = 0; - private _tableBlockCurrentIndex = 0; + private _children: DataStreamTreeNode[] = []; + private _editArea: DocumentEditArea = DocumentEditArea.BODY; private readonly _editAreaChange$ = new BehaviorSubject>(null); readonly editAreaChange$ = this._editAreaChange$.asObservable(); - headerTreeMap: Map = new Map(); - footerTreeMap: Map = new Map(); + private _headerTreeMap: Map = new Map(); + private _footerTreeMap: Map = new Map(); private readonly _segmentViewModels$ = new BehaviorSubject([]); readonly segmentViewModels$ = this._segmentViewModels$.asObservable(); @@ -203,9 +212,9 @@ export class DocumentViewModel implements IDisposable { const body = _documentDataModel.getBody()!; const { sectionList, tableNodeCache } = parseDataStreamToTree(body.dataStream, body.tables); - this._buildTextRunsCache(); + this._buildAllCache(); - this.children = sectionList; + this._children = sectionList; this._tableNodeCache = tableNodeCache; this._buildHeaderFooterViewModel(); @@ -213,19 +222,28 @@ export class DocumentViewModel implements IDisposable { registerCustomRangeInterceptor(interceptor: ICustomRangeInterceptor): IDisposable { this._interceptor = interceptor; + return toDisposable(() => this._interceptor = null); } dispose(): void { - this.children.forEach((child) => { + this._children.forEach((child) => { child.dispose(); }); this._textRunsCache.clear(); + this._paragraphCache.clear(); + this._sectionBreakCache.clear(); + this._customBlockCache.clear(); + this._tableCache.clear(); + this._tableNodeCache.clear(); } - selfPlus(_len: number, _index: number) { - // empty + getHeaderFooterTreeMap() { + return { + headerTreeMap: this._headerTreeMap, + footerTreeMap: this._footerTreeMap, + }; } getEditArea() { @@ -239,12 +257,8 @@ export class DocumentViewModel implements IDisposable { } } - getPositionInParent() { - return 0; - } - - getLastIndex() { - return this.children[this.children.length - 1].endIndex; + getChildren() { + return this._children; } getBody() { @@ -264,12 +278,12 @@ export class DocumentViewModel implements IDisposable { return this as DocumentViewModel; } - if (this.headerTreeMap.has(segmentId)) { - return this.headerTreeMap.get(segmentId)!; + if (this._headerTreeMap.has(segmentId)) { + return this._headerTreeMap.get(segmentId)!; } - if (this.footerTreeMap.has(segmentId)) { - return this.footerTreeMap.get(segmentId)!; + if (this._footerTreeMap.has(segmentId)) { + return this._footerTreeMap.get(segmentId)!; } return this as DocumentViewModel; @@ -282,188 +296,22 @@ export class DocumentViewModel implements IDisposable { const { sectionList, tableNodeCache } = parseDataStreamToTree(body.dataStream, body.tables); - this.children = sectionList; + this._children = sectionList; this._tableNodeCache = tableNodeCache; - this._buildTextRunsCache(); + this._buildAllCache(); this._buildHeaderFooterViewModel(); } - insert(insertBody: IDocumentBody, insertIndex = 0) { - const dataStream = insertBody.dataStream; - let dataStreamLen = dataStream.length; - const insertedNode = this._getParagraphByIndex(this.children, insertIndex); - - if (insertedNode == null) { - return; - } - - if (dataStream[dataStreamLen - 1] === DataStreamTreeTokenType.SECTION_BREAK) { - const docDataModel = new DocumentDataModel({ body: insertBody }); - const insertBodyModel = new DocumentViewModel(docDataModel); - - dataStreamLen -= 1; // sectionBreak can not be inserted - - const insertNodes = insertBodyModel.children; - - for (const node of insertNodes) { - this._forEachDown(node, (newNode) => { - newNode.plus(insertIndex); - }); - } - - const insertedNodeSplit = insertedNode.split(insertIndex); - - if (insertedNodeSplit == null) { - return; - } - - const { firstNode: insertedFirstNode, lastNode: insertedLastNode } = insertedNodeSplit; - - insertedNode.parent?.children.splice( - insertedNode.getPositionInParent(), - 1, - insertedFirstNode, - ...insertNodes, - insertedLastNode - ); - - this._forEachTop(insertedNode.parent, (currentNode) => { - // currentNode.endIndex += dataStreamLen; - currentNode.selfPlus(dataStreamLen, currentNode.getPositionInParent()); - const children = currentNode.children; - let isStartFix = false; - - for (const node of children) { - if (node === insertedLastNode) { - isStartFix = true; - } - - if (!isStartFix) { - continue; - } - - this._forEachDown(node, (newNode) => { - newNode.plus(dataStreamLen); - }); - } - }); - } else if (dataStreamLen === 1 && dataStream[dataStreamLen - 1] === DataStreamTreeTokenType.PARAGRAPH) { - this._insertParagraph(insertedNode, insertIndex); - } else { - insertedNode.insertText(dataStream, insertIndex); - - // insertedNode.endIndex += dataStreamLen; - insertedNode.selfPlus(dataStreamLen, insertIndex); - - this._forEachTop(insertedNode.parent, (currentNode) => { - // currentNode.endIndex += dataStreamLen; - currentNode.selfPlus(dataStreamLen, currentNode.getPositionInParent()); - const children = currentNode.children; - let isStartFix = false; - for (const node of children) { - if (node.startIndex > insertIndex) { - isStartFix = true; - } - - if (!isStartFix) { - continue; - } - - this._forEachDown(node, (newNode) => { - newNode.plus(dataStreamLen); - }); - } - }); - } - } - - delete(currentIndex: number, textLength: number) { - const nodes = this.children; - - this._deleteTree(nodes, currentIndex, textLength); - } - - /** Get pure text content in the given range. */ - getText(): string { - // Basically this is a DFS traversal of the tree to get the `content` and append it to the result. - // TODO: implement - const pieces: string[] = []; - - function traverseTreeNode(node: DataStreamTreeNode) { - if (node.content) { - pieces.push(node.content); - } - - node.children.forEach(traverseTreeNode); - } - - this.children.forEach((n) => traverseTreeNode(n)); - - return pieces.join(''); - } - - resetCache() { - this._sectionBreakCurrentIndex = 0; - this._paragraphCurrentIndex = 0; - this._customBlockCurrentIndex = 0; - this._tableBlockCurrentIndex = 0; - - if (this.headerTreeMap.size > 0) { - for (const header of this.headerTreeMap.values()) { - header.resetCache(); - } - } - - if (this.footerTreeMap.size > 0) { - for (const footer of this.footerTreeMap.values()) { - footer.resetCache(); - } - } - } - getSectionBreak(index: number) { - if (index == null) { - return; - } - const sectionBreaks = this.getBody()!.sectionBreaks; - if (sectionBreaks == null) { - return; - } - - for (let i = this._sectionBreakCurrentIndex; i < sectionBreaks.length; i++) { - const sectionBreak = sectionBreaks[i]; - if (sectionBreak.startIndex === index) { - this._sectionBreakCurrentIndex = i; - - return sectionBreak; - } - } + return this._sectionBreakCache.get(index); } - // TODO: @jocs, Use hash map to instead of array. - getParagraph(index: number, fromStart = false) { - const paragraphs = this.getBody()!.paragraphs; - if (paragraphs == null) { - return; - } - - for (let i = fromStart ? 0 : this._paragraphCurrentIndex; i < paragraphs.length; i++) { - const paragraph = paragraphs[i]; - if (paragraph.startIndex === index) { - if (!fromStart) { - this._paragraphCurrentIndex = i; - } - - return paragraph; - } - } + getParagraph(index: number) { + return this._paragraphCache.get(index); } - /** - * textRun matches according to the selection. If the text length is 10, then the range of textRun is from 0 to 11. - */ getTextRun(index: number): Nullable { const cacheIndex = Math.floor(index / this._cacheSize); const textRunsCache = this._textRunsCache.get(cacheIndex); @@ -472,19 +320,7 @@ export class DocumentViewModel implements IDisposable { } getCustomBlock(index: number) { - const customBlocks = this.getBody()!.customBlocks; - if (customBlocks == null) { - return; - } - - for (let i = this._customBlockCurrentIndex; i < customBlocks.length; i++) { - const customBlock = customBlocks[i]; - if (customBlock.startIndex === index) { - this._customBlockCurrentIndex = i; - - return customBlock; - } - } + return this._customBlockCache.get(index); } getCustomBlockWithoutSetCurrentIndex(index: number) { @@ -500,27 +336,8 @@ export class DocumentViewModel implements IDisposable { } } - getTable(index: number) { - const tables = this.getBody()?.tables; - const tableSource = this.getSnapshot().tableSource; - if (tables == null || tableSource == null) { - return; - } - - let tableId: Nullable = null; - - for (let i = this._tableBlockCurrentIndex; i < tables.length; i++) { - const table = tables[i]; - if (table.startIndex === index) { - this._tableBlockCurrentIndex = i; - tableId = table.tableId; - break; - } - } - - if (tableId != null && tableSource[tableId] != null) { - return tableSource[tableId]; - } + getTableByStartIndex(index: number) { + return this._tableCache.get(index); } findTableNodeById(id: string) { @@ -571,215 +388,101 @@ export class DocumentViewModel implements IDisposable { return this.getCustomDecorationRaw(index); } - private _buildTextRunsCache() { - const textRuns = this.getBody()?.textRuns ?? []; - this._textRunsCache.clear(); - - for (const textRun of textRuns) { - const { st, ed } = textRun; - - for (let i = st; i < ed; i++) { - const cacheIndex = Math.floor(i / this._cacheSize); - - if (!this._textRunsCache.has(cacheIndex)) { - this._textRunsCache.set(cacheIndex, new Map()); - } - - this._textRunsCache.get(cacheIndex)!.set(i % this._cacheSize, textRun); - } - } + private _buildAllCache() { + this._buildTextRunsCache(); + this._buildParagraphCache(); + this._buildSectionBreakCache(); + this._buildCustomBlockCache(); + this._buildTableCache(); } - private _buildHeaderFooterViewModel() { - const { headerModelMap, footerModelMap } = this._documentDataModel; - const viewModels = []; - for (const [headerId, headerModel] of headerModelMap) { - this.headerTreeMap.set(headerId, new DocumentViewModel(headerModel)); - viewModels.push(this.headerTreeMap.get(headerId)!); - } + private _buildParagraphCache() { + this._paragraphCache.clear(); - for (const [footerId, footerModel] of footerModelMap) { - this.footerTreeMap.set(footerId, new DocumentViewModel(footerModel)); - viewModels.push(this.footerTreeMap.get(footerId)!); - } + const paragraphs = this.getBody()?.paragraphs ?? []; - this._segmentViewModels$.next(viewModels); + for (const paragraph of paragraphs) { + const { startIndex } = paragraph; + this._paragraphCache.set(startIndex, paragraph); + } } - private _getParagraphByIndex(nodes: DataStreamTreeNode[], insertIndex: number): Nullable { - for (const node of nodes) { - const { children } = node; - - if (node.exclude(insertIndex)) { - continue; - } + private _buildSectionBreakCache() { + this._sectionBreakCache.clear(); + const sectionBreaks = this.getBody()?.sectionBreaks ?? []; - if (node.nodeType === DataStreamTreeNodeType.PARAGRAPH) { - return node; - } - - return this._getParagraphByIndex(children, insertIndex); + for (const sectionBreak of sectionBreaks) { + const { startIndex } = sectionBreak; + this._sectionBreakCache.set(startIndex, sectionBreak); } - - return null; } - private _forEachTop( - node: Nullable, - func: (node: DataStreamTreeNode | DocumentViewModel) => void - ) { - let parent: Nullable = node; + private _buildCustomBlockCache() { + this._customBlockCache.clear(); + const customBlocks = this.getBody()?.customBlocks ?? []; - while (parent) { - func(parent); - parent = parent.parent; + for (const customBlock of customBlocks) { + const { startIndex } = customBlock; + this._customBlockCache.set(startIndex, customBlock); } - - func(this); } - private _forEachDown(node: DataStreamTreeNode, func: (node: DataStreamTreeNode) => void) { - func(node); - - const children = node.children; + private _buildTableCache() { + this._tableCache.clear(); - for (node of children) { - this._forEachDown(node, func); + const tables = this.getBody()?.tables; + const tableConfig = this.getSnapshot().tableSource; + if (tables == null || tableConfig == null) { + return; } - } - private _deleteTree(nodes: DataStreamTreeNode[], currentIndex: number, textLength: number) { - const startIndex = currentIndex; - const endIndex = currentIndex + textLength - 1; - let mergeNode: Nullable = null; - let nodeCount = nodes.length; - let i = 0; - - while (i < nodeCount) { - const node = nodes[i]; - const { startIndex: st, endIndex: ed, children } = node; - - this._deleteTree(children, currentIndex, textLength); - - if (startIndex === endIndex && endIndex === ed) { - // The cursor is at the dividing point between two paragraphs, - // and it is necessary to determine whether to delete elements - // such as paragraphs, chapters, and tables - if (node.nodeType === DataStreamTreeNodeType.PARAGRAPH) { - const nextNode = this._getNextNode(node); - if (nextNode == null) { - i++; - continue; - } + for (const table of tables) { + const { startIndex, tableId } = table; + const tableSource = tableConfig[tableId]; - // if (nextNode.isBullet || nextNode.isIndent) { - // i++; - // continue; - // } else { - node.minus(startIndex, endIndex); - node.merge(nextNode); - nodeCount--; - // } - } - // else if (node.nodeType === DataStreamTreeNodeType.SECTION_BREAK) { - // } else if (node.nodeType === DataStreamTreeNodeType.TABLE) { - // } else if (node.nodeType === DataStreamTreeNodeType.TABLE_ROW) { - // } else if (node.nodeType === DataStreamTreeNodeType.TABLE_CELL) { - // } - } else if (startIndex <= st && endIndex >= ed) { - // The first case. The selection range of the text box - // is larger than the current node - node.remove(); - nodeCount--; + if (tableSource == null) { continue; - } else if (st <= startIndex && ed >= endIndex) { - // The second case. The selection range of - // the text box is smaller than the current node - node.minus(startIndex, endIndex); - } else if (endIndex > st && endIndex < ed) { - // The third case. - // The text selection left contains the current node - node.minus(st, endIndex); - if (mergeNode != null) { - mergeNode.merge(node); - mergeNode = null; - nodeCount--; - continue; - } - } else if (startIndex > st && startIndex < ed) { - // The fourth case. - // The text selection right contains the current node - node.minus(startIndex, ed); - mergeNode = node; - } else if (st > endIndex) { - // The current node is not on the right side of - // the selection area and needs to be moved as a whole - node.plus(-textLength); } - i++; + + this._tableCache.set(startIndex, { + table, + tableSource, + }); } } - private _getNextNode(node: DataStreamTreeNode): DataStreamTreeNode { - const currentIndex = node.getPositionInParent(); - const children = node.parent?.children; - return children?.[currentIndex + 1] as DataStreamTreeNode; - } + private _buildTextRunsCache() { + const textRuns = this.getBody()?.textRuns ?? []; + this._textRunsCache.clear(); - private _insertParagraph(insertedNode: DataStreamTreeNode, insertIndex = 0) { - const insertStartIndex = insertedNode.startIndex; + for (const textRun of textRuns) { + const { st, ed } = textRun; - const insertEndIndex = insertedNode.endIndex; + for (let i = st; i < ed; i++) { + const cacheIndex = Math.floor(i / this._cacheSize); - const insertedNodeSplit = insertedNode.split(insertIndex); + if (!this._textRunsCache.has(cacheIndex)) { + this._textRunsCache.set(cacheIndex, new Map()); + } - if (insertedNodeSplit == null) { - return; + this._textRunsCache.get(cacheIndex)!.set(i % this._cacheSize, textRun); + } } + } - const { firstNode: insertedFirstNode, lastNode: insertedLastNode } = insertedNodeSplit; - - insertedFirstNode.content += DataStreamTreeTokenType.PARAGRAPH; - - insertedFirstNode.selfPlus(1); - - insertedFirstNode.plus(insertStartIndex); - - this._forEachDown(insertedLastNode, (newNode) => { - newNode.plus(insertStartIndex + 1); - }); - - insertedNode.parent?.children.splice( - insertedNode.getPositionInParent(), - 1, - insertedFirstNode, - insertedLastNode - ); - - this._forEachTop(insertedNode.parent, (currentNode) => { - // currentNode.endIndex += dataStreamLen; - currentNode.selfPlus(1, currentNode.getPositionInParent()); - const children = currentNode.children; - let isStartFix = false; - - for (const node of children) { - // `insertedLastNode` no need to fix, because it already add 1. - if (node === insertedLastNode) { - continue; - } - - if (node.startIndex >= insertEndIndex + 1) { - isStartFix = true; - } + private _buildHeaderFooterViewModel() { + const { headerModelMap, footerModelMap } = this._documentDataModel; + const viewModels = []; + for (const [headerId, headerModel] of headerModelMap) { + this._headerTreeMap.set(headerId, new DocumentViewModel(headerModel)); + viewModels.push(this._headerTreeMap.get(headerId)!); + } - if (!isStartFix) { - continue; - } + for (const [footerId, footerModel] of footerModelMap) { + this._footerTreeMap.set(footerId, new DocumentViewModel(footerModel)); + viewModels.push(this._footerTreeMap.get(footerId)!); + } - this._forEachDown(node, (newNode) => { - newNode.plus(1); - }); - } - }); + this._segmentViewModels$.next(viewModels); } }