Path: blob/main/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
5221 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { ISequence, LcsDiff } from '../../../../../base/common/diff/diff.js';6import { Emitter, Event, PauseableEmitter } from '../../../../../base/common/event.js';7import { hash } from '../../../../../base/common/hash.js';8import { Disposable, dispose, IDisposable } from '../../../../../base/common/lifecycle.js';9import { Schemas } from '../../../../../base/common/network.js';10import { filter } from '../../../../../base/common/objects.js';11import { isEqual } from '../../../../../base/common/resources.js';12import { hasKey, isDefined } from '../../../../../base/common/types.js';13import { URI } from '../../../../../base/common/uri.js';14import { Position } from '../../../../../editor/common/core/position.js';15import { Range } from '../../../../../editor/common/core/range.js';16import { ILanguageService } from '../../../../../editor/common/languages/language.js';17import { FindMatch, ITextModel } from '../../../../../editor/common/model.js';18import { TextModel } from '../../../../../editor/common/model/textModel.js';19import { SearchParams } from '../../../../../editor/common/model/textModelSearch.js';20import { IModelService } from '../../../../../editor/common/services/model.js';21import { IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js';22import { IResourceUndoRedoElement, IUndoRedoElement, IUndoRedoService, IWorkspaceUndoRedoElement, UndoRedoElementType, UndoRedoGroup } from '../../../../../platform/undoRedo/common/undoRedo.js';23import { ILanguageDetectionService } from '../../../../services/languageDetection/common/languageDetectionWorkerService.js';24import { SnapshotContext } from '../../../../services/workingCopy/common/fileWorkingCopy.js';25import { CellEditType, CellUri, diff, ICell, ICellDto2, ICellEditOperation, ICellOutput, INotebookSnapshotOptions, INotebookTextModel, IOutputDto, IOutputItemDto, ISelectionState, NotebookCellDefaultCollapseConfig, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, NotebookCellsChangeType, NotebookCellTextModelSplice, NotebookData, NotebookDocumentMetadata, NotebookTextModelChangedEvent, NotebookTextModelWillAddRemoveEvent, NullablePartialNotebookCellInternalMetadata, NullablePartialNotebookCellMetadata, TransientOptions } from '../notebookCommon.js';26import { INotebookExecutionStateService } from '../notebookExecutionStateService.js';27import { INotebookLoggingService } from '../notebookLoggingService.js';28import { CellMetadataEdit, MoveCellEdit, SpliceCellsEdit } from './cellEdit.js';29import { NotebookCellOutputTextModel } from './notebookCellOutputTextModel.js';30import { NotebookCellTextModel } from './notebookCellTextModel.js';3132class StackOperation implements IWorkspaceUndoRedoElement {33type: UndoRedoElementType.Workspace;34tag = 'notebookUndoRedoElement';3536public get code() {37return this._operations.length === 1 ? this._operations[0].code : 'undoredo.notebooks.stackOperation';38}3940private _operations: IUndoRedoElement[] = [];41private _beginSelectionState: ISelectionState | undefined = undefined;42private _resultSelectionState: ISelectionState | undefined = undefined;43private _beginAlternativeVersionId: string;44private _resultAlternativeVersionId: string;45public get label() {46return this._operations.length === 1 ? this._operations[0].label : 'edit';47}4849constructor(50readonly textModel: NotebookTextModel,51readonly undoRedoGroup: UndoRedoGroup | undefined,52private readonly _pauseableEmitter: PauseableEmitter<NotebookTextModelChangedEvent>,53private readonly _postUndoRedo: (alternativeVersionId: string) => void,54selectionState: ISelectionState | undefined,55beginAlternativeVersionId: string56) {57this.type = UndoRedoElementType.Workspace;58this._beginSelectionState = selectionState;59this._beginAlternativeVersionId = beginAlternativeVersionId;60this._resultAlternativeVersionId = beginAlternativeVersionId;61}62get resources(): readonly URI[] {63return [this.textModel.uri];64}6566get isEmpty(): boolean {67return this._operations.length === 0;68}6970pushEndState(alternativeVersionId: string, selectionState: ISelectionState | undefined) {71// https://github.com/microsoft/vscode/issues/20752372this._resultAlternativeVersionId = alternativeVersionId;73this._resultSelectionState = selectionState || this._resultSelectionState;74}7576pushEditOperation(element: IUndoRedoElement, beginSelectionState: ISelectionState | undefined, resultSelectionState: ISelectionState | undefined, alternativeVersionId: string) {77if (this._operations.length === 0) {78this._beginSelectionState = this._beginSelectionState ?? beginSelectionState;79}80this._operations.push(element);81this._resultSelectionState = resultSelectionState;82this._resultAlternativeVersionId = alternativeVersionId;83}8485async undo(): Promise<void> {86this._pauseableEmitter.pause();87try {88for (let i = this._operations.length - 1; i >= 0; i--) {89await this._operations[i].undo();90}91this._postUndoRedo(this._beginAlternativeVersionId);92this._pauseableEmitter.fire({93rawEvents: [],94synchronous: undefined,95versionId: this.textModel.versionId,96endSelectionState: this._beginSelectionState97});98} finally {99this._pauseableEmitter.resume();100}101}102103async redo(): Promise<void> {104this._pauseableEmitter.pause();105try {106for (let i = 0; i < this._operations.length; i++) {107await this._operations[i].redo();108}109this._postUndoRedo(this._resultAlternativeVersionId);110this._pauseableEmitter.fire({111rawEvents: [],112synchronous: undefined,113versionId: this.textModel.versionId,114endSelectionState: this._resultSelectionState115});116} finally {117this._pauseableEmitter.resume();118}119120}121}122123class NotebookOperationManager {124private _pendingStackOperation: StackOperation | null = null;125private _isAppending: boolean = false;126constructor(127private readonly _textModel: NotebookTextModel,128private _undoService: IUndoRedoService,129private _pauseableEmitter: PauseableEmitter<NotebookTextModelChangedEvent>,130private _postUndoRedo: (alternativeVersionId: string) => void131) {132}133134isUndoStackEmpty(): boolean {135return this._pendingStackOperation === null || this._pendingStackOperation.isEmpty;136}137138pushStackElement(alternativeVersionId: string, selectionState: ISelectionState | undefined) {139if (this._pendingStackOperation && !this._pendingStackOperation.isEmpty) {140this._pendingStackOperation.pushEndState(alternativeVersionId, selectionState);141if (!this._isAppending) {142this._undoService.pushElement(this._pendingStackOperation, this._pendingStackOperation.undoRedoGroup);143}144}145this._isAppending = false;146this._pendingStackOperation = null;147}148149private _getOrCreateEditStackElement(beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, alternativeVersionId: string) {150return this._pendingStackOperation ??= new StackOperation(this._textModel, undoRedoGroup, this._pauseableEmitter, this._postUndoRedo, beginSelectionState, alternativeVersionId || '');151}152153appendPreviousOperation(): boolean {154const previous = this._undoService.getLastElement(this._textModel.uri) as StackOperation;155if (previous && previous.tag === 'notebookUndoRedoElement') {156this._pendingStackOperation = previous;157this._isAppending = true;158return true;159}160return false;161}162163pushEditOperation(element: IUndoRedoElement, beginSelectionState: ISelectionState | undefined, resultSelectionState: ISelectionState | undefined, alternativeVersionId: string, undoRedoGroup: UndoRedoGroup | undefined) {164const pendingStackOperation = this._getOrCreateEditStackElement(beginSelectionState, undoRedoGroup, alternativeVersionId);165pendingStackOperation.pushEditOperation(element, beginSelectionState, resultSelectionState, alternativeVersionId);166}167}168169type TransformedEdit = {170edit: ICellEditOperation;171cellIndex: number;172end: number | undefined;173originalIndex: number;174};175176class NotebookEventEmitter extends PauseableEmitter<NotebookTextModelChangedEvent> {177get isEmpty() {178return this._eventQueue.isEmpty();179}180181isDirtyEvent() {182for (const e of this._eventQueue) {183for (let i = 0; i < e.rawEvents.length; i++) {184if (!e.rawEvents[i].transient) {185return true;186}187}188}189190return false;191}192}193194export class NotebookTextModel extends Disposable implements INotebookTextModel {195196private _isDisposed = false;197private readonly _onWillDispose: Emitter<void> = this._register(new Emitter<void>());198private readonly _onWillAddRemoveCells = this._register(new Emitter<NotebookTextModelWillAddRemoveEvent>());199private readonly _onDidChangeContent = this._register(new Emitter<NotebookTextModelChangedEvent>());200readonly onWillDispose: Event<void> = this._onWillDispose.event;201readonly onWillAddRemoveCells = this._onWillAddRemoveCells.event;202readonly onDidChangeContent = this._onDidChangeContent.event;203private _cellhandlePool: number = 0;204private readonly _cellListeners: Map<number, IDisposable> = new Map();205private _cells: NotebookCellTextModel[] = [];206private _defaultCollapseConfig: NotebookCellDefaultCollapseConfig | undefined;207208metadata: NotebookDocumentMetadata = {};209transientOptions: TransientOptions = { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} };210private _versionId = 0;211212/**213* This alternative id is only for non-cell-content changes.214*/215private _notebookSpecificAlternativeId = 0;216217/**218* Unlike, versionId, this can go down (via undo) or go to previous values (via redo)219*/220private _alternativeVersionId: string = '1';221private _operationManager: NotebookOperationManager;222private _pauseableEmitter: NotebookEventEmitter;223224get length() {225return this._cells.length;226}227228get cells(): readonly NotebookCellTextModel[] {229return this._cells;230}231232get versionId() {233return this._versionId;234}235236get alternativeVersionId(): string {237return this._alternativeVersionId;238}239240get notebookType() {241return this.viewType;242}243244constructor(245readonly viewType: string,246readonly uri: URI,247cells: ICellDto2[],248metadata: NotebookDocumentMetadata,249options: TransientOptions,250@IUndoRedoService private readonly _undoService: IUndoRedoService,251@IModelService private readonly _modelService: IModelService,252@ILanguageService private readonly _languageService: ILanguageService,253@ILanguageDetectionService private readonly _languageDetectionService: ILanguageDetectionService,254@INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService,255@INotebookLoggingService private readonly _notebookLoggingService: INotebookLoggingService256) {257super();258this.transientOptions = options;259this.metadata = metadata;260this._initialize(cells);261262const maybeUpdateCellTextModel = (textModel: ITextModel) => {263if (textModel.uri.scheme === Schemas.vscodeNotebookCell && textModel instanceof TextModel) {264const cellUri = CellUri.parse(textModel.uri);265if (cellUri && isEqual(cellUri.notebook, this.uri)) {266const cellIdx = this._getCellIndexByHandle(cellUri.handle);267if (cellIdx >= 0) {268const cell = this.cells[cellIdx];269if (cell) {270cell.textModel = textModel;271}272}273}274}275};276this._register(_modelService.onModelAdded(e => maybeUpdateCellTextModel(e)));277278this._pauseableEmitter = this._register(new NotebookEventEmitter({279merge: (events: NotebookTextModelChangedEvent[]) => {280const first = events[0];281282const rawEvents = first.rawEvents;283let versionId = first.versionId;284let endSelectionState = first.endSelectionState;285let synchronous = first.synchronous;286287for (let i = 1; i < events.length; i++) {288rawEvents.push(...events[i].rawEvents);289versionId = events[i].versionId;290endSelectionState = events[i].endSelectionState !== undefined ? events[i].endSelectionState : endSelectionState;291synchronous = events[i].synchronous !== undefined ? events[i].synchronous : synchronous;292}293294return { rawEvents, versionId, endSelectionState, synchronous };295}296}));297298this._register(this._pauseableEmitter.event(e => {299if (e.rawEvents.length) {300this._onDidChangeContent.fire(e);301}302}));303304this._operationManager = new NotebookOperationManager(305this,306this._undoService,307this._pauseableEmitter,308(alternativeVersionId: string) => {309this._increaseVersionId(true);310this._overwriteAlternativeVersionId(alternativeVersionId);311}312);313314this._notebookLoggingService.trace('notebookTextModel', `Initialized notebook text model for ${uri.toString()}`);315}316317setCellCollapseDefault(collapseConfig: NotebookCellDefaultCollapseConfig | undefined) {318this._defaultCollapseConfig = collapseConfig;319}320321_initialize(cells: ICellDto2[], triggerDirty?: boolean) {322this._cells = [];323this._versionId = 0;324this._notebookSpecificAlternativeId = 0;325326const mainCells = cells.map(cell => {327const cellHandle = this._cellhandlePool++;328const cellUri = CellUri.generate(this.uri, cellHandle);329return new NotebookCellTextModel(330cellUri,331cellHandle,332cell,333this.transientOptions,334this._languageService,335this._modelService.getCreationOptions(cell.language, cellUri, false).defaultEOL,336this._defaultCollapseConfig,337this._languageDetectionService,338this._notebookLoggingService339);340});341342for (let i = 0; i < mainCells.length; i++) {343const dirtyStateListener = mainCells[i].onDidChangeContent((e) => {344this._bindCellContentHandler(mainCells[i], e);345});346347this._cellListeners.set(mainCells[i].handle, dirtyStateListener);348this._register(mainCells[i]);349}350351this._cells.splice(0, 0, ...mainCells);352this._alternativeVersionId = this._generateAlternativeId();353354if (triggerDirty) {355this._pauseableEmitter.fire({356rawEvents: [{ kind: NotebookCellsChangeType.Unknown, transient: false }],357versionId: this.versionId,358synchronous: true,359endSelectionState: undefined360});361}362}363364private _bindCellContentHandler(cell: NotebookCellTextModel, e: 'content' | 'language' | 'mime' | { type: 'model'; event: IModelContentChangedEvent }) {365this._increaseVersionId(e === 'content' || (typeof e === 'object' && e.type === 'model'));366switch (e) {367case 'content':368this._pauseableEmitter.fire({369rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellContent, index: this._getCellIndexByHandle(cell.handle), transient: false }],370versionId: this.versionId,371synchronous: true,372endSelectionState: undefined373});374break;375376case 'language':377this._pauseableEmitter.fire({378rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellLanguage, index: this._getCellIndexByHandle(cell.handle), language: cell.language, transient: false }],379versionId: this.versionId,380synchronous: true,381endSelectionState: undefined382});383break;384385case 'mime':386this._pauseableEmitter.fire({387rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellMime, index: this._getCellIndexByHandle(cell.handle), mime: cell.mime, transient: false }],388versionId: this.versionId,389synchronous: true,390endSelectionState: undefined391});392break;393394default:395if (typeof e === 'object' && e.type === 'model') {396this._pauseableEmitter.fire({397rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellContent, index: this._getCellIndexByHandle(cell.handle), transient: false }],398versionId: this.versionId,399synchronous: true,400endSelectionState: undefined401});402}403break;404}405}406407private _generateAlternativeId() {408return `${this._notebookSpecificAlternativeId}_` + this.cells.map(cell => cell.handle + ',' + cell.alternativeId).join(';');409}410411override dispose() {412if (this._isDisposed) {413// NotebookEditorModel can be disposed twice, don't fire onWillDispose again414return;415}416417this._isDisposed = true;418this._onWillDispose.fire();419this._undoService.removeElements(this.uri);420421dispose(this._cellListeners.values());422this._cellListeners.clear();423424dispose(this._cells);425this._cells = [];426super.dispose();427}428429pushStackElement() {430// https://github.com/microsoft/vscode/issues/207523431}432433private _getCellIndexByHandle(handle: number) {434return this.cells.findIndex(c => c.handle === handle);435}436437private _getCellIndexWithOutputIdHandleFromEdits(outputId: string, rawEdits: ICellEditOperation[]) {438const edit = rawEdits.find(e => hasKey(e, { outputs: true }) && e.outputs.some(o => o.outputId === outputId));439if (edit) {440if (hasKey(edit, { index: true })) {441return edit.index;442} else if (hasKey(edit, { handle: true })) {443const cellIndex = this._getCellIndexByHandle(edit.handle);444this._assertIndex(cellIndex);445return cellIndex;446}447}448449return -1;450}451452private _getCellIndexWithOutputIdHandle(outputId: string) {453return this.cells.findIndex(c => !!c.outputs.find(o => o.outputId === outputId));454}455456reset(cells: ICellDto2[], metadata: NotebookDocumentMetadata, transientOptions: TransientOptions): void {457this.transientOptions = transientOptions;458const executions = this._notebookExecutionStateService.getCellExecutionsForNotebook(this.uri);459const executingCellHandles = executions.filter(exe => exe.state === NotebookCellExecutionState.Executing).map(exe => exe.cellHandle);460const edits = NotebookTextModel.computeEdits(this, cells, executingCellHandles);461462this.applyEdits(463[464...edits,465{ editType: CellEditType.DocumentMetadata, metadata }466],467true,468undefined, () => undefined,469undefined,470false471);472}473474createSnapshot(options: INotebookSnapshotOptions): NotebookData {475const transientOptions = options.transientOptions ?? this.transientOptions;476const data: NotebookData = {477metadata: filter(this.metadata, key => !transientOptions.transientDocumentMetadata[key]),478cells: [],479};480481let outputSize = 0;482for (const cell of this.cells) {483const cellData: ICellDto2 = {484cellKind: cell.cellKind,485language: cell.language,486mime: cell.mime,487source: cell.getValue(),488outputs: [],489internalMetadata: cell.internalMetadata490};491492if (options.context === SnapshotContext.Backup && options.outputSizeLimit > 0) {493cell.outputs.forEach(output => {494output.outputs.forEach(item => {495outputSize += item.data.byteLength;496});497});498if (outputSize > options.outputSizeLimit) {499throw new Error('Notebook too large to backup');500}501}502503cellData.outputs = !transientOptions.transientOutputs ? cell.outputs : [];504cellData.metadata = filter(cell.metadata, key => !transientOptions.transientCellMetadata[key]);505506data.cells.push(cellData);507}508509return data;510}511512restoreSnapshot(snapshot: NotebookData, transientOptions?: TransientOptions): void {513this.reset(snapshot.cells, snapshot.metadata, transientOptions ?? this.transientOptions);514}515516static computeEdits(model: NotebookTextModel, cells: ICellDto2[], executingHandles: number[] = []): ICellEditOperation[] {517const edits: ICellEditOperation[] = [];518const isExecuting = (cell: NotebookCellTextModel) => executingHandles.includes(cell.handle);519520const commonPrefix = this._commonPrefix(model.cells, model.cells.length, 0, cells, cells.length, 0, isExecuting);521522if (commonPrefix > 0) {523for (let i = 0; i < commonPrefix; i++) {524edits.push(525{526editType: CellEditType.Metadata,527index: i,528metadata: cells[i].metadata ?? {}529},530...this._computeOutputEdit(i, model.cells[i].outputs, cells[i].outputs)531);532}533}534535if (model.cells.length === cells.length && commonPrefix === model.cells.length) {536return edits;537}538539const commonSuffix = this._commonSuffix(model.cells, model.cells.length - commonPrefix, commonPrefix, cells, cells.length - commonPrefix, commonPrefix, isExecuting);540541if (commonSuffix > 0) {542edits.push({ editType: CellEditType.Replace, index: commonPrefix, count: model.cells.length - commonPrefix - commonSuffix, cells: cells.slice(commonPrefix, cells.length - commonSuffix) });543} else if (commonPrefix > 0) {544edits.push({ editType: CellEditType.Replace, index: commonPrefix, count: model.cells.length - commonPrefix, cells: cells.slice(commonPrefix) });545} else {546edits.push({ editType: CellEditType.Replace, index: 0, count: model.cells.length, cells });547}548549if (commonSuffix > 0) {550// has same suffix551for (let i = commonSuffix; i > 0; i--) {552edits.push(553{554editType: CellEditType.Metadata,555index: model.cells.length - i,556metadata: cells[cells.length - i].metadata ?? {}557},558...this._computeOutputEdit(model.cells.length - i, model.cells[model.cells.length - i].outputs, cells[cells.length - i].outputs)559);560}561}562563return edits;564}565566private static _computeOutputEdit(index: number, a: ICellOutput[], b: IOutputDto[]): ICellEditOperation[] {567if (a.length !== b.length) {568return [569{570editType: CellEditType.Output,571index: index,572outputs: b,573append: false574}575];576}577578if (a.length === 0) {579// no output580return [];581}582583// same length584return b.map((output, i) => {585return {586editType: CellEditType.OutputItems,587outputId: a[i].outputId,588items: output.outputs,589append: false590};591});592}593594private static _commonPrefix(a: readonly NotebookCellTextModel[], aLen: number, aDelta: number, b: ICellDto2[], bLen: number, bDelta: number, isExecuting: (cell: NotebookCellTextModel) => boolean): number {595const maxResult = Math.min(aLen, bLen);596let result = 0;597for (let i = 0; i < maxResult && a[aDelta + i].fastEqual(b[bDelta + i], isExecuting(a[aDelta + i])); i++) {598result++;599}600601return result;602}603604private static _commonSuffix(a: readonly NotebookCellTextModel[], aLen: number, aDelta: number, b: ICellDto2[], bLen: number, bDelta: number, isExecuting: (cell: NotebookCellTextModel) => boolean): number {605const maxResult = Math.min(aLen, bLen);606let result = 0;607for (let i = 0; i < maxResult && a[aDelta + aLen - i - 1].fastEqual(b[bDelta + bLen - i - 1], isExecuting(a[aDelta + aLen - i - 1])); i++) {608result++;609}610return result;611}612613private newCellsFromLastEdit = new Set<number>();614private isOnlyEditingMetadataOnNewCells(rawEdits: ICellEditOperation[]): boolean {615for (const edit of rawEdits) {616if (edit.editType === CellEditType.PartialInternalMetadata) {617continue;618}619if (edit.editType !== CellEditType.Metadata && edit.editType !== CellEditType.PartialMetadata) {620return false;621}622623if (hasKey(edit, { index: true }) && !this.newCellsFromLastEdit.has(this.cells[edit.index].handle)) {624return false;625}626if (hasKey(edit, { handle: true }) && !this.newCellsFromLastEdit.has(edit.handle)) {627return false;628}629}630631return true;632}633634applyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: ISelectionState | undefined, endSelectionsComputer: () => ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, computeUndoRedo: boolean): boolean {635this._notebookLoggingService.trace('textModelEdits', `Begin applying ${rawEdits.length} raw edits`);636this._pauseableEmitter.pause();637try {638this._operationManager.pushStackElement(this._alternativeVersionId, undefined);639640if (computeUndoRedo && this.isOnlyEditingMetadataOnNewCells(rawEdits)) {641if (!this._operationManager.appendPreviousOperation()) {642// we can't append the previous operation, so just don't compute undo/redo643computeUndoRedo = false;644}645} else if (computeUndoRedo) {646this.newCellsFromLastEdit.clear();647}648649try {650this._doApplyEdits(rawEdits, synchronous, computeUndoRedo, beginSelectionState, undoRedoGroup);651return true;652} catch (err) {653this._notebookLoggingService.error('textModelEdits', `Error while applying edits: ${err}`);654throw err;655} finally {656if (!this._pauseableEmitter.isEmpty) {657// Update selection and versionId after applying edits.658const endSelections = endSelectionsComputer();659this._increaseVersionId(this._operationManager.isUndoStackEmpty() && !this._pauseableEmitter.isDirtyEvent());660661// Finalize undo element662this._operationManager.pushStackElement(this._alternativeVersionId, endSelections);663664// Broadcast changes665this._pauseableEmitter.fire({ rawEvents: [], versionId: this.versionId, synchronous: synchronous, endSelectionState: endSelections });666this._notebookLoggingService.trace('textModelEdits', `End applying ${rawEdits.length} raw edits`);667}668}669} finally {670this._pauseableEmitter.resume();671}672}673674private _doApplyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined): void {675const editsWithDetails = rawEdits.map((edit, index) => {676let cellIndex: number = -1;677if (hasKey(edit, { index: true })) {678cellIndex = edit.index;679} else if (hasKey(edit, { handle: true })) {680cellIndex = this._getCellIndexByHandle(edit.handle);681this._assertIndex(cellIndex);682} else if (hasKey(edit, { outputId: true })) {683cellIndex = this._getCellIndexWithOutputIdHandle(edit.outputId);684if (this._indexIsInvalid(cellIndex)) {685// The referenced output may have been created in this batch of edits686cellIndex = this._getCellIndexWithOutputIdHandleFromEdits(edit.outputId, rawEdits.slice(0, index));687}688689if (this._indexIsInvalid(cellIndex)) {690// It's possible for an edit to refer to an output which was just cleared, ignore it without throwing691return null;692}693} else if (edit.editType !== CellEditType.DocumentMetadata) {694throw new Error('Invalid cell edit: ' + JSON.stringify(edit));695}696697return {698edit,699cellIndex,700end:701(edit.editType === CellEditType.DocumentMetadata)702? undefined703: (edit.editType === CellEditType.Replace ? edit.index + edit.count : cellIndex),704originalIndex: index705};706}).filter(isDefined);707708// compress all edits which have no side effects on cell index709const edits = this._mergeCellEdits(editsWithDetails)710.sort((a, b) => {711if (a.end === undefined) {712return -1;713}714715if (b.end === undefined) {716return -1;717}718719return b.end - a.end || b.originalIndex - a.originalIndex;720}).reduce((prev, curr) => {721if (!prev.length) {722// empty723prev.push([curr]);724} else {725const last = prev[prev.length - 1];726const index = last[0].cellIndex;727728if (curr.cellIndex === index) {729last.push(curr);730} else {731prev.push([curr]);732}733}734735return prev;736}, [] as TransformedEdit[][]).map(editsOnSameIndex => {737const replaceEdits: TransformedEdit[] = [];738const otherEdits: TransformedEdit[] = [];739740editsOnSameIndex.forEach(edit => {741if (edit.edit.editType === CellEditType.Replace) {742replaceEdits.push(edit);743} else {744otherEdits.push(edit);745}746});747748return [...otherEdits.reverse(), ...replaceEdits];749});750751const flattenEdits = edits.flat();752753for (const { edit, cellIndex } of flattenEdits) {754switch (edit.editType) {755case CellEditType.Replace:756this._replaceCells(edit.index, edit.count, edit.cells, synchronous, computeUndoRedo, beginSelectionState, undoRedoGroup);757break;758case CellEditType.Output: {759this._assertIndex(cellIndex);760const cell = this._cells[cellIndex];761if (edit.append) {762this._spliceNotebookCellOutputs(cell, { start: cell.outputs.length, deleteCount: 0, newOutputs: edit.outputs.map(op => new NotebookCellOutputTextModel(op)) }, true, computeUndoRedo);763} else {764this._spliceNotebookCellOutputs2(cell, edit.outputs, computeUndoRedo);765}766break;767}768case CellEditType.OutputItems:769{770this._assertIndex(cellIndex);771const cell = this._cells[cellIndex];772if (edit.append) {773this._appendNotebookCellOutputItems(cell, edit.outputId, edit.items);774} else {775this._replaceNotebookCellOutputItems(cell, edit.outputId, edit.items);776}777}778break;779780case CellEditType.Metadata:781this._assertIndex(edit.index);782this._changeCellMetadata(this._cells[edit.index], edit.metadata, computeUndoRedo, beginSelectionState, undoRedoGroup);783break;784case CellEditType.PartialMetadata:785this._assertIndex(cellIndex);786this._changeCellMetadataPartial(this._cells[cellIndex], edit.metadata, computeUndoRedo, beginSelectionState, undoRedoGroup);787break;788case CellEditType.PartialInternalMetadata:789this._assertIndex(cellIndex);790this._changeCellInternalMetadataPartial(this._cells[cellIndex], edit.internalMetadata);791break;792case CellEditType.CellLanguage:793this._assertIndex(edit.index);794this._changeCellLanguage(this._cells[edit.index], edit.language, computeUndoRedo, beginSelectionState, undoRedoGroup);795break;796case CellEditType.DocumentMetadata:797this._updateNotebookCellMetadata(edit.metadata, computeUndoRedo, beginSelectionState, undoRedoGroup);798break;799case CellEditType.Move:800this._moveCellToIdx(edit.index, edit.length, edit.newIdx, synchronous, computeUndoRedo, beginSelectionState, undefined, undoRedoGroup);801break;802}803}804}805806private _mergeCellEdits(rawEdits: TransformedEdit[]): TransformedEdit[] {807const mergedEdits: TransformedEdit[] = [];808809rawEdits.forEach(edit => {810if (mergedEdits.length) {811const last = mergedEdits[mergedEdits.length - 1];812813if (last.edit.editType === CellEditType.Output814&& last.edit.append815&& edit.edit.editType === CellEditType.Output816&& edit.edit.append817&& last.cellIndex === edit.cellIndex818) {819last.edit.outputs = [...last.edit.outputs, ...edit.edit.outputs];820} else if (last.edit.editType === CellEditType.Output821&& !last.edit.append // last cell is not append822&& last.edit.outputs.length === 0 // last cell is clear outputs823&& edit.edit.editType === CellEditType.Output824&& edit.edit.append825&& last.cellIndex === edit.cellIndex826) {827last.edit.append = false;828last.edit.outputs = edit.edit.outputs;829} else {830mergedEdits.push(edit);831}832} else {833mergedEdits.push(edit);834}835});836837return mergedEdits;838}839840private _replaceCells(index: number, count: number, cellDtos: ICellDto2[], synchronous: boolean, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined): void {841842if (count === 0 && cellDtos.length === 0) {843return;844}845846const oldViewCells = this._cells.slice(0);847const oldSet = new Set();848oldViewCells.forEach(cell => {849oldSet.add(cell.handle);850});851852// prepare remove853for (let i = index; i < Math.min(index + count, this._cells.length); i++) {854const cell = this._cells[i];855this._cellListeners.get(cell.handle)?.dispose();856this._cellListeners.delete(cell.handle);857}858859// prepare add860const cells = cellDtos.map(cellDto => {861const cellHandle = this._cellhandlePool++;862const cellUri = CellUri.generate(this.uri, cellHandle);863if (!cellDto.outputs) {864cellDto.outputs = [];865}866const cell = new NotebookCellTextModel(867cellUri,868cellHandle,869cellDto,870this.transientOptions,871this._languageService,872this._modelService.getCreationOptions(cellDto.language, cellUri, false).defaultEOL,873this._defaultCollapseConfig,874this._languageDetectionService,875this._notebookLoggingService876);877const textModel = this._modelService.getModel(cellUri);878if (textModel && textModel instanceof TextModel) {879cell.textModel = textModel;880cell.language = cellDto.language;881cell.textModel.setValue(cellDto.source);882cell.resetTextBuffer(cell.textModel.getTextBuffer());883}884const dirtyStateListener = cell.onDidChangeContent((e) => {885this._bindCellContentHandler(cell, e);886});887888this.newCellsFromLastEdit.add(cell.handle);889this._cellListeners.set(cell.handle, dirtyStateListener);890this._register(cell);891return cell;892});893894// compute change895const cellsCopy = this._cells.slice(0);896cellsCopy.splice(index, count, ...cells);897const diffs = diff(this._cells, cellsCopy, cell => {898return oldSet.has(cell.handle);899}).map(diff => {900return [diff.start, diff.deleteCount, diff.toInsert] as [number, number, NotebookCellTextModel[]];901});902this._onWillAddRemoveCells.fire({ rawEvent: { kind: NotebookCellsChangeType.ModelChange, changes: diffs } });903904// make change905this._cells = cellsCopy;906907const undoDiff = diffs.map(diff => {908const deletedCells = oldViewCells.slice(diff[0], diff[0] + diff[1]);909910return [diff[0], deletedCells, diff[2]] as [number, NotebookCellTextModel[], NotebookCellTextModel[]];911});912913if (computeUndoRedo) {914this._operationManager.pushEditOperation(new SpliceCellsEdit(this.uri, undoDiff, {915insertCell: (index, cell, endSelections) => { this._insertNewCell(index, [cell], true, endSelections); },916deleteCell: (index, endSelections) => { this._removeCell(index, 1, true, endSelections); },917replaceCell: (index, count, cells, endSelections) => { this._replaceNewCells(index, count, cells, true, endSelections); },918}, undefined, undefined), beginSelectionState, undefined, this._alternativeVersionId, undoRedoGroup);919}920921// should be deferred922this._pauseableEmitter.fire({923rawEvents: [{ kind: NotebookCellsChangeType.ModelChange, changes: diffs, transient: false }],924versionId: this.versionId,925synchronous: synchronous,926endSelectionState: undefined927});928}929930private _increaseVersionId(transient: boolean): void {931this._versionId = this._versionId + 1;932if (!transient) {933this._notebookSpecificAlternativeId = this._versionId;934}935this._alternativeVersionId = this._generateAlternativeId();936}937938private _overwriteAlternativeVersionId(newAlternativeVersionId: string): void {939this._alternativeVersionId = newAlternativeVersionId;940this._notebookSpecificAlternativeId = Number(newAlternativeVersionId.substring(0, newAlternativeVersionId.indexOf('_')));941}942943private _updateNotebookCellMetadata(metadata: NotebookDocumentMetadata, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined) {944const oldMetadata = this.metadata;945const triggerDirtyChange = this._isDocumentMetadataChanged(this.metadata, metadata);946947if (triggerDirtyChange) {948if (computeUndoRedo) {949const that = this;950this._operationManager.pushEditOperation(new class implements IResourceUndoRedoElement {951readonly type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;952get resource() {953return that.uri;954}955readonly label = 'Update Cell Metadata';956readonly code = 'undoredo.textBufferEdit';957undo() {958that._updateNotebookCellMetadata(oldMetadata, false, beginSelectionState, undoRedoGroup);959}960redo() {961that._updateNotebookCellMetadata(metadata, false, beginSelectionState, undoRedoGroup);962}963}(), beginSelectionState, undefined, this._alternativeVersionId, undoRedoGroup);964}965}966967this.metadata = metadata;968this._pauseableEmitter.fire({969rawEvents: [{ kind: NotebookCellsChangeType.ChangeDocumentMetadata, metadata: this.metadata, transient: !triggerDirtyChange }],970versionId: this.versionId,971synchronous: true,972endSelectionState: undefined973});974}975976private _insertNewCell(index: number, cells: NotebookCellTextModel[], synchronous: boolean, endSelections: ISelectionState | undefined): void {977for (let i = 0; i < cells.length; i++) {978const dirtyStateListener = cells[i].onDidChangeContent((e) => {979this._bindCellContentHandler(cells[i], e);980});981982this._cellListeners.set(cells[i].handle, dirtyStateListener);983}984985const changes: NotebookCellTextModelSplice<ICell>[] = [[index, 0, cells]];986this._onWillAddRemoveCells.fire({ rawEvent: { kind: NotebookCellsChangeType.ModelChange, changes } });987this._cells.splice(index, 0, ...cells);988this._pauseableEmitter.fire({989rawEvents: [{ kind: NotebookCellsChangeType.ModelChange, changes, transient: false }],990versionId: this.versionId,991synchronous: synchronous,992endSelectionState: endSelections993});994995return;996}997998private _removeCell(index: number, count: number, synchronous: boolean, endSelections: ISelectionState | undefined) {999for (let i = index; i < index + count; i++) {1000const cell = this._cells[i];1001this._cellListeners.get(cell.handle)?.dispose();1002this._cellListeners.delete(cell.handle);1003}1004const changes: NotebookCellTextModelSplice<ICell>[] = [[index, count, []]];1005this._onWillAddRemoveCells.fire({ rawEvent: { kind: NotebookCellsChangeType.ModelChange, changes } });1006this._cells.splice(index, count);1007this._pauseableEmitter.fire({1008rawEvents: [{ kind: NotebookCellsChangeType.ModelChange, changes, transient: false }],1009versionId: this.versionId,1010synchronous: synchronous,1011endSelectionState: endSelections1012});1013}10141015private _replaceNewCells(index: number, count: number, cells: NotebookCellTextModel[], synchronous: boolean, endSelections: ISelectionState | undefined) {1016for (let i = index; i < index + count; i++) {1017const cell = this._cells[i];1018this._cellListeners.get(cell.handle)?.dispose();1019this._cellListeners.delete(cell.handle);1020}10211022for (let i = 0; i < cells.length; i++) {1023const dirtyStateListener = cells[i].onDidChangeContent((e) => {1024this._bindCellContentHandler(cells[i], e);1025});10261027this._cellListeners.set(cells[i].handle, dirtyStateListener);1028}10291030const changes: NotebookCellTextModelSplice<ICell>[] = [[index, count, cells]];1031this._onWillAddRemoveCells.fire({ rawEvent: { kind: NotebookCellsChangeType.ModelChange, changes } });1032this._cells.splice(index, count, ...cells);1033this._pauseableEmitter.fire({1034rawEvents: [{ kind: NotebookCellsChangeType.ModelChange, changes, transient: false }],1035versionId: this.versionId,1036synchronous: synchronous,1037endSelectionState: endSelections1038});1039}10401041private _isDocumentMetadataChanged(a: NotebookDocumentMetadata, b: NotebookDocumentMetadata) {1042const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})]);1043for (const key of keys) {1044if (key === 'custom') {1045if (!this._customMetadataEqual(a[key], b[key])1046&&1047!(this.transientOptions.transientDocumentMetadata[key as keyof NotebookDocumentMetadata])1048) {1049return true;1050}1051} else if (1052(a[key as keyof NotebookDocumentMetadata] !== b[key as keyof NotebookDocumentMetadata])1053&&1054!(this.transientOptions.transientDocumentMetadata[key as keyof NotebookDocumentMetadata])1055) {1056return true;1057}1058}10591060return false;1061}10621063private _isCellMetadataChanged(a: NotebookCellMetadata, b: NotebookCellMetadata) {1064const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})]);1065for (const key of keys) {1066if (1067(a[key as keyof NotebookCellMetadata] !== b[key as keyof NotebookCellMetadata])1068&&1069!(this.transientOptions.transientCellMetadata[key as keyof NotebookCellMetadata])1070) {1071return true;1072}1073}10741075return false;1076}10771078private _customMetadataEqual(a: any, b: any) {1079if (!a && !b) {1080// both of them are nullish or undefined1081return true;1082}10831084if (!a || !b) {1085return false;1086}10871088const aProps = Object.getOwnPropertyNames(a);1089const bProps = Object.getOwnPropertyNames(b);10901091if (aProps.length !== bProps.length) {1092return false;1093}10941095for (let i = 0; i < aProps.length; i++) {1096const propName = aProps[i];1097if (a[propName] !== b[propName]) {1098return false;1099}1100}11011102return true;1103}11041105private _changeCellMetadataPartial(cell: NotebookCellTextModel, metadata: NullablePartialNotebookCellMetadata, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined) {1106const newMetadata: NotebookCellMetadata = {1107...cell.metadata1108};1109let k: keyof NullablePartialNotebookCellMetadata;1110for (k in metadata) {1111const value = metadata[k] ?? undefined;1112newMetadata[k] = value;1113}11141115return this._changeCellMetadata(cell, newMetadata, computeUndoRedo, beginSelectionState, undoRedoGroup);1116}11171118private _changeCellMetadata(cell: NotebookCellTextModel, metadata: NotebookCellMetadata, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined) {1119const triggerDirtyChange = this._isCellMetadataChanged(cell.metadata, metadata);11201121if (triggerDirtyChange) {1122if (computeUndoRedo) {1123const index = this._cells.indexOf(cell);1124this._operationManager.pushEditOperation(new CellMetadataEdit(this.uri, index, Object.freeze(cell.metadata), Object.freeze(metadata), {1125updateCellMetadata: (index, newMetadata) => {1126const cell = this._cells[index];1127if (!cell) {1128return;1129}1130this._changeCellMetadata(cell, newMetadata, false, beginSelectionState, undoRedoGroup);1131}1132}), beginSelectionState, undefined, this._alternativeVersionId, undoRedoGroup);1133}1134}11351136// should be deferred1137cell.metadata = metadata;1138this._pauseableEmitter.fire({1139rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellMetadata, index: this._cells.indexOf(cell), metadata: cell.metadata, transient: !triggerDirtyChange }],1140versionId: this.versionId,1141synchronous: true,1142endSelectionState: undefined1143});1144}11451146private _changeCellInternalMetadataPartial(cell: NotebookCellTextModel, internalMetadata: NullablePartialNotebookCellInternalMetadata) {1147const newInternalMetadata: NotebookCellInternalMetadata = {1148...cell.internalMetadata1149};1150let k: keyof NotebookCellInternalMetadata;1151for (k in internalMetadata) {1152const value = internalMetadata[k] ?? undefined;1153(newInternalMetadata[k] as unknown) = value;1154}11551156cell.internalMetadata = newInternalMetadata;1157this._pauseableEmitter.fire({1158rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellInternalMetadata, index: this._cells.indexOf(cell), internalMetadata: cell.internalMetadata, transient: true }],1159versionId: this.versionId,1160synchronous: true,1161endSelectionState: undefined1162});1163}11641165private _changeCellLanguage(cell: NotebookCellTextModel, languageId: string, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined) {1166if (cell.language === languageId) {1167return;1168}11691170const oldLanguage = cell.language;1171cell.language = languageId;11721173if (computeUndoRedo) {1174const that = this;1175this._operationManager.pushEditOperation(new class implements IResourceUndoRedoElement {1176readonly type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;1177get resource() {1178return that.uri;1179}1180readonly label = 'Update Cell Language';1181readonly code = 'undoredo.textBufferEdit';1182undo() {1183that._changeCellLanguage(cell, oldLanguage, false, beginSelectionState, undoRedoGroup);1184}1185redo() {1186that._changeCellLanguage(cell, languageId, false, beginSelectionState, undoRedoGroup);1187}1188}(), beginSelectionState, undefined, this._alternativeVersionId, undoRedoGroup);1189}11901191this._pauseableEmitter.fire({1192rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellLanguage, index: this._cells.indexOf(cell), language: languageId, transient: false }],1193versionId: this.versionId,1194synchronous: true,1195endSelectionState: undefined1196});1197}11981199private _spliceNotebookCellOutputs2(cell: NotebookCellTextModel, outputs: IOutputDto[], computeUndoRedo: boolean): void {1200if (outputs.length === 0 && cell.outputs.length === 0) {1201return;1202}12031204if (outputs.length <= 1) {1205this._spliceNotebookCellOutputs(cell, { start: 0, deleteCount: cell.outputs.length, newOutputs: outputs.map(op => new NotebookCellOutputTextModel(op)) }, false, computeUndoRedo);1206return;1207}12081209const diff = new LcsDiff(new OutputSequence(cell.outputs), new OutputSequence(outputs));1210const diffResult = diff.ComputeDiff(false);1211const splices: NotebookCellOutputsSplice[] = diffResult.changes.map(change => ({1212start: change.originalStart,1213deleteCount: change.originalLength,1214// create cell output text model only when it's inserted into the notebook document1215newOutputs: outputs.slice(change.modifiedStart, change.modifiedStart + change.modifiedLength).map(op => new NotebookCellOutputTextModel(op))1216}));1217splices.reverse().forEach(splice => {1218this._spliceNotebookCellOutputs(cell, splice, false, computeUndoRedo);1219});1220}12211222private _spliceNotebookCellOutputs(cell: NotebookCellTextModel, splice: NotebookCellOutputsSplice, append: boolean, computeUndoRedo: boolean): void {1223cell.spliceNotebookCellOutputs(splice);1224this._pauseableEmitter.fire({1225rawEvents: [{1226kind: NotebookCellsChangeType.Output,1227index: this._cells.indexOf(cell),1228outputs: cell.outputs.map(output => output.asDto()) ?? [],1229append,1230transient: this.transientOptions.transientOutputs,1231}],1232versionId: this.versionId,1233synchronous: true,1234endSelectionState: undefined1235});1236}12371238private _appendNotebookCellOutputItems(cell: NotebookCellTextModel, outputId: string, items: IOutputItemDto[]) {1239if (cell.changeOutputItems(outputId, true, items)) {1240this._pauseableEmitter.fire({1241rawEvents: [{1242kind: NotebookCellsChangeType.OutputItem,1243index: this._cells.indexOf(cell),1244outputId: outputId,1245outputItems: items,1246append: true,1247transient: this.transientOptions.transientOutputs12481249}],1250versionId: this.versionId,1251synchronous: true,1252endSelectionState: undefined1253});1254}1255}12561257private _replaceNotebookCellOutputItems(cell: NotebookCellTextModel, outputId: string, items: IOutputItemDto[]) {1258if (cell.changeOutputItems(outputId, false, items)) {1259this._pauseableEmitter.fire({1260rawEvents: [{1261kind: NotebookCellsChangeType.OutputItem,1262index: this._cells.indexOf(cell),1263outputId: outputId,1264outputItems: items,1265append: false,1266transient: this.transientOptions.transientOutputs12671268}],1269versionId: this.versionId,1270synchronous: true,1271endSelectionState: undefined1272});1273}1274}12751276private _moveCellToIdx(index: number, length: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean, beforeSelections: ISelectionState | undefined, endSelections: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined): boolean {1277if (pushedToUndoStack) {1278this._operationManager.pushEditOperation(new MoveCellEdit(this.uri, index, length, newIdx, {1279moveCell: (fromIndex: number, length: number, toIndex: number, beforeSelections: ISelectionState | undefined, endSelections: ISelectionState | undefined) => {1280this._moveCellToIdx(fromIndex, length, toIndex, true, false, beforeSelections, endSelections, undoRedoGroup);1281},1282}, beforeSelections, endSelections), beforeSelections, endSelections, this._alternativeVersionId, undoRedoGroup);1283}12841285this._assertIndex(index);1286this._assertIndex(newIdx);12871288const cells = this._cells.splice(index, length);1289this._cells.splice(newIdx, 0, ...cells);1290this._pauseableEmitter.fire({1291rawEvents: [{ kind: NotebookCellsChangeType.Move, index, length, newIdx, cells, transient: false }],1292versionId: this.versionId,1293synchronous: synchronous,1294endSelectionState: endSelections1295});12961297return true;1298}12991300private _assertIndex(index: number) {1301if (this._indexIsInvalid(index)) {1302throw new Error(`model index out of range ${index}`);1303}1304}13051306private _indexIsInvalid(index: number): boolean {1307return index < 0 || index >= this._cells.length;1308}13091310//#region Find1311findNextMatch(searchString: string, searchStart: { cellIndex: number; position: Position }, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, searchEnd?: { cellIndex: number; position: Position }): { cell: NotebookCellTextModel; match: FindMatch } | null {1312// check if search cell index is valid1313this._assertIndex(searchStart.cellIndex);1314const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);1315const searchData = searchParams.parseSearchRequest();13161317if (!searchData) {1318return null;1319}13201321let cellIndex = searchStart.cellIndex;1322let searchStartPosition = searchStart.position;13231324let searchEndCell = this._cells.length;13251326while (cellIndex < searchEndCell) {1327const cell = this._cells[cellIndex];13281329// if we have wrapped back to the point of the initial search cell, we search from beginning to the provided searchEnd position1330const wrapFlag = searchEnd && cellIndex === searchEnd.cellIndex && searchStartPosition.isBefore(searchEnd.position);1331const searchRange = new Range(1332searchStartPosition.lineNumber,1333searchStartPosition.column,1334(wrapFlag) ? searchEnd.position.lineNumber : cell.textBuffer.getLineCount(),1335(wrapFlag) ? searchEnd.position.column : cell.textBuffer.getLineMaxColumn(cell.textBuffer.getLineCount())1336);13371338const result = cell.textBuffer.findMatchesLineByLine(searchRange, searchData, false, 1);1339if (result.length > 0) {1340return { cell, match: result[0] };1341} else if (wrapFlag) { // this means there are no more valid matches in the notebook1342break;1343}13441345// Move to the next cell1346cellIndex++;13471348// wrap if a searchEnd is provided and we are past the end of the notebook1349if (searchEnd && cellIndex >= this._cells.length) {1350cellIndex = 0;1351searchEndCell = searchEnd.cellIndex + 1;1352}13531354searchStartPosition = new Position(1, 1); // Reset position to start of the next cell1355}13561357return null;1358}13591360findMatches(searchString: string, isRegex: boolean, matchCase: boolean, wordSeparators: string | null): { cell: NotebookCellTextModel; matches: FindMatch[] }[] {1361const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);1362const searchData = searchParams.parseSearchRequest();13631364if (!searchData) {1365return [];1366}13671368const results: { cell: NotebookCellTextModel; matches: FindMatch[] }[] = [];1369for (const cell of this._cells) {1370const searchRange = new Range(1, 1, cell.textBuffer.getLineCount(), cell.textBuffer.getLineMaxColumn(cell.textBuffer.getLineCount()));1371const matches = cell.textBuffer.findMatchesLineByLine(searchRange, searchData, false, 1000);13721373if (matches.length > 0) {1374results.push({ cell, matches: matches });1375}1376}13771378return results;1379}1380//#endregion1381}13821383class OutputSequence implements ISequence {1384constructor(readonly outputs: IOutputDto[]) {1385}13861387getElements(): Int32Array | number[] | string[] {1388return this.outputs.map(output => {1389return hash(output.outputs.map(output => ({1390mime: output.mime,1391data: output.data1392})));1393});1394}13951396}139713981399