Path: blob/main/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
3296 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 { 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, CellKind, CellUri, diff, ICell, ICellDto2, ICellEditOperation, ICellOutput, INotebookSnapshotOptions, INotebookTextModel, IOutputDto, IOutputItemDto, ISelectionState, NotebookCellCollapseState, NotebookCellDefaultCollapseConfig, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, NotebookCellsChangeType, NotebookCellTextModelSplice, NotebookData, NotebookDocumentMetadata, NotebookTextModelChangedEvent, NotebookTextModelWillAddRemoveEvent, NullablePartialNotebookCellInternalMetadata, NullablePartialNotebookCellMetadata, TransientOptions } from '../notebookCommon.js';26import { INotebookExecutionStateService } from '../notebookExecutionStateService.js';27import { CellMetadataEdit, MoveCellEdit, SpliceCellsEdit } from './cellEdit.js';28import { NotebookCellOutputTextModel } from './notebookCellOutputTextModel.js';29import { NotebookCellTextModel } from './notebookCellTextModel.js';3031class StackOperation implements IWorkspaceUndoRedoElement {32type: UndoRedoElementType.Workspace;33tag = 'notebookUndoRedoElement';3435public get code() {36return this._operations.length === 1 ? this._operations[0].code : 'undoredo.notebooks.stackOperation';37}3839private _operations: IUndoRedoElement[] = [];40private _beginSelectionState: ISelectionState | undefined = undefined;41private _resultSelectionState: ISelectionState | undefined = undefined;42private _beginAlternativeVersionId: string;43private _resultAlternativeVersionId: string;44public get label() {45return this._operations.length === 1 ? this._operations[0].label : 'edit';46}4748constructor(49readonly textModel: NotebookTextModel,50readonly undoRedoGroup: UndoRedoGroup | undefined,51private readonly _pauseableEmitter: PauseableEmitter<NotebookTextModelChangedEvent>,52private readonly _postUndoRedo: (alternativeVersionId: string) => void,53selectionState: ISelectionState | undefined,54beginAlternativeVersionId: string55) {56this.type = UndoRedoElementType.Workspace;57this._beginSelectionState = selectionState;58this._beginAlternativeVersionId = beginAlternativeVersionId;59this._resultAlternativeVersionId = beginAlternativeVersionId;60}61get resources(): readonly URI[] {62return [this.textModel.uri];63}6465get isEmpty(): boolean {66return this._operations.length === 0;67}6869pushEndState(alternativeVersionId: string, selectionState: ISelectionState | undefined) {70// https://github.com/microsoft/vscode/issues/20752371this._resultAlternativeVersionId = alternativeVersionId;72this._resultSelectionState = selectionState || this._resultSelectionState;73}7475pushEditOperation(element: IUndoRedoElement, beginSelectionState: ISelectionState | undefined, resultSelectionState: ISelectionState | undefined, alternativeVersionId: string) {76if (this._operations.length === 0) {77this._beginSelectionState = this._beginSelectionState ?? beginSelectionState;78}79this._operations.push(element);80this._resultSelectionState = resultSelectionState;81this._resultAlternativeVersionId = alternativeVersionId;82}8384async undo(): Promise<void> {85this._pauseableEmitter.pause();86try {87for (let i = this._operations.length - 1; i >= 0; i--) {88await this._operations[i].undo();89}90this._postUndoRedo(this._beginAlternativeVersionId);91this._pauseableEmitter.fire({92rawEvents: [],93synchronous: undefined,94versionId: this.textModel.versionId,95endSelectionState: this._beginSelectionState96});97} finally {98this._pauseableEmitter.resume();99}100}101102async redo(): Promise<void> {103this._pauseableEmitter.pause();104try {105for (let i = 0; i < this._operations.length; i++) {106await this._operations[i].redo();107}108this._postUndoRedo(this._resultAlternativeVersionId);109this._pauseableEmitter.fire({110rawEvents: [],111synchronous: undefined,112versionId: this.textModel.versionId,113endSelectionState: this._resultSelectionState114});115} finally {116this._pauseableEmitter.resume();117}118119}120}121122class NotebookOperationManager {123private _pendingStackOperation: StackOperation | null = null;124private _isAppending: boolean = false;125constructor(126private readonly _textModel: NotebookTextModel,127private _undoService: IUndoRedoService,128private _pauseableEmitter: PauseableEmitter<NotebookTextModelChangedEvent>,129private _postUndoRedo: (alternativeVersionId: string) => void130) {131}132133isUndoStackEmpty(): boolean {134return this._pendingStackOperation === null || this._pendingStackOperation.isEmpty;135}136137pushStackElement(alternativeVersionId: string, selectionState: ISelectionState | undefined) {138if (this._pendingStackOperation && !this._pendingStackOperation.isEmpty) {139this._pendingStackOperation.pushEndState(alternativeVersionId, selectionState);140if (!this._isAppending) {141this._undoService.pushElement(this._pendingStackOperation, this._pendingStackOperation.undoRedoGroup);142}143}144this._isAppending = false;145this._pendingStackOperation = null;146}147148private _getOrCreateEditStackElement(beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, alternativeVersionId: string) {149return this._pendingStackOperation ??= new StackOperation(this._textModel, undoRedoGroup, this._pauseableEmitter, this._postUndoRedo, beginSelectionState, alternativeVersionId || '');150}151152appendPreviousOperation(): boolean {153const previous = this._undoService.getLastElement(this._textModel.uri) as StackOperation;154if (previous && previous.tag === 'notebookUndoRedoElement') {155this._pendingStackOperation = previous;156this._isAppending = true;157return true;158}159return false;160}161162pushEditOperation(element: IUndoRedoElement, beginSelectionState: ISelectionState | undefined, resultSelectionState: ISelectionState | undefined, alternativeVersionId: string, undoRedoGroup: UndoRedoGroup | undefined) {163const pendingStackOperation = this._getOrCreateEditStackElement(beginSelectionState, undoRedoGroup, alternativeVersionId);164pendingStackOperation.pushEditOperation(element, beginSelectionState, resultSelectionState, alternativeVersionId);165}166}167168type TransformedEdit = {169edit: ICellEditOperation;170cellIndex: number;171end: number | undefined;172originalIndex: number;173};174175class NotebookEventEmitter extends PauseableEmitter<NotebookTextModelChangedEvent> {176get isEmpty() {177return this._eventQueue.isEmpty();178}179180isDirtyEvent() {181for (const e of this._eventQueue) {182for (let i = 0; i < e.rawEvents.length; i++) {183if (!e.rawEvents[i].transient) {184return true;185}186}187}188189return false;190}191}192193export class NotebookTextModel extends Disposable implements INotebookTextModel {194195private _isDisposed = false;196private readonly _onWillDispose: Emitter<void> = this._register(new Emitter<void>());197private readonly _onWillAddRemoveCells = this._register(new Emitter<NotebookTextModelWillAddRemoveEvent>());198private readonly _onDidChangeContent = this._register(new Emitter<NotebookTextModelChangedEvent>());199readonly onWillDispose: Event<void> = this._onWillDispose.event;200readonly onWillAddRemoveCells = this._onWillAddRemoveCells.event;201readonly onDidChangeContent = this._onDidChangeContent.event;202private _cellhandlePool: number = 0;203private readonly _cellListeners: Map<number, IDisposable> = new Map();204private _cells: NotebookCellTextModel[] = [];205private _defaultCollapseConfig: NotebookCellDefaultCollapseConfig | undefined;206207metadata: NotebookDocumentMetadata = {};208transientOptions: TransientOptions = { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} };209private _versionId = 0;210211/**212* This alternative id is only for non-cell-content changes.213*/214private _notebookSpecificAlternativeId = 0;215216/**217* Unlike, versionId, this can go down (via undo) or go to previous values (via redo)218*/219private _alternativeVersionId: string = '1';220private _operationManager: NotebookOperationManager;221private _pauseableEmitter: NotebookEventEmitter;222223get length() {224return this._cells.length;225}226227get cells(): readonly NotebookCellTextModel[] {228return this._cells;229}230231get versionId() {232return this._versionId;233}234235get alternativeVersionId(): string {236return this._alternativeVersionId;237}238239get notebookType() {240return this.viewType;241}242243constructor(244readonly viewType: string,245readonly uri: URI,246cells: ICellDto2[],247metadata: NotebookDocumentMetadata,248options: TransientOptions,249@IUndoRedoService private readonly _undoService: IUndoRedoService,250@IModelService private readonly _modelService: IModelService,251@ILanguageService private readonly _languageService: ILanguageService,252@ILanguageDetectionService private readonly _languageDetectionService: ILanguageDetectionService,253@INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService,254) {255super();256this.transientOptions = options;257this.metadata = metadata;258this._initialize(cells);259260const maybeUpdateCellTextModel = (textModel: ITextModel) => {261if (textModel.uri.scheme === Schemas.vscodeNotebookCell && textModel instanceof TextModel) {262const cellUri = CellUri.parse(textModel.uri);263if (cellUri && isEqual(cellUri.notebook, this.uri)) {264const cellIdx = this._getCellIndexByHandle(cellUri.handle);265if (cellIdx >= 0) {266const cell = this.cells[cellIdx];267if (cell) {268cell.textModel = textModel;269}270}271}272}273};274this._register(_modelService.onModelAdded(e => maybeUpdateCellTextModel(e)));275276this._pauseableEmitter = new NotebookEventEmitter({277merge: (events: NotebookTextModelChangedEvent[]) => {278const first = events[0];279280const rawEvents = first.rawEvents;281let versionId = first.versionId;282let endSelectionState = first.endSelectionState;283let synchronous = first.synchronous;284285for (let i = 1; i < events.length; i++) {286rawEvents.push(...events[i].rawEvents);287versionId = events[i].versionId;288endSelectionState = events[i].endSelectionState !== undefined ? events[i].endSelectionState : endSelectionState;289synchronous = events[i].synchronous !== undefined ? events[i].synchronous : synchronous;290}291292return { rawEvents, versionId, endSelectionState, synchronous };293}294});295296this._register(this._pauseableEmitter.event(e => {297if (e.rawEvents.length) {298this._onDidChangeContent.fire(e);299}300}));301302this._operationManager = new NotebookOperationManager(303this,304this._undoService,305this._pauseableEmitter,306(alternativeVersionId: string) => {307this._increaseVersionId(true);308this._overwriteAlternativeVersionId(alternativeVersionId);309}310);311}312313setCellCollapseDefault(collapseConfig: NotebookCellDefaultCollapseConfig | undefined) {314this._defaultCollapseConfig = collapseConfig;315}316317_initialize(cells: ICellDto2[], triggerDirty?: boolean) {318this._cells = [];319this._versionId = 0;320this._notebookSpecificAlternativeId = 0;321322const mainCells = cells.map(cell => {323const cellHandle = this._cellhandlePool++;324const cellUri = CellUri.generate(this.uri, cellHandle);325const collapseState = this._getDefaultCollapseState(cell);326return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.mime, cell.cellKind, cell.outputs, cell.metadata, cell.internalMetadata, collapseState, this.transientOptions, this._languageService,327this._modelService.getCreationOptions(cell.language, cellUri, false).defaultEOL, this._languageDetectionService);328});329330for (let i = 0; i < mainCells.length; i++) {331const dirtyStateListener = mainCells[i].onDidChangeContent((e) => {332this._bindCellContentHandler(mainCells[i], e);333});334335this._cellListeners.set(mainCells[i].handle, dirtyStateListener);336this._register(mainCells[i]);337}338339this._cells.splice(0, 0, ...mainCells);340this._alternativeVersionId = this._generateAlternativeId();341342if (triggerDirty) {343this._pauseableEmitter.fire({344rawEvents: [{ kind: NotebookCellsChangeType.Unknown, transient: false }],345versionId: this.versionId,346synchronous: true,347endSelectionState: undefined348});349}350}351352private _bindCellContentHandler(cell: NotebookCellTextModel, e: 'content' | 'language' | 'mime' | { type: 'model'; event: IModelContentChangedEvent }) {353this._increaseVersionId(e === 'content' || (typeof e === 'object' && e.type === 'model'));354switch (e) {355case 'content':356this._pauseableEmitter.fire({357rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellContent, index: this._getCellIndexByHandle(cell.handle), transient: false }],358versionId: this.versionId,359synchronous: true,360endSelectionState: undefined361});362break;363364case 'language':365this._pauseableEmitter.fire({366rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellLanguage, index: this._getCellIndexByHandle(cell.handle), language: cell.language, transient: false }],367versionId: this.versionId,368synchronous: true,369endSelectionState: undefined370});371break;372373case 'mime':374this._pauseableEmitter.fire({375rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellMime, index: this._getCellIndexByHandle(cell.handle), mime: cell.mime, transient: false }],376versionId: this.versionId,377synchronous: true,378endSelectionState: undefined379});380break;381382default:383if (typeof e === 'object' && e.type === 'model') {384this._pauseableEmitter.fire({385rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellContent, index: this._getCellIndexByHandle(cell.handle), transient: false }],386versionId: this.versionId,387synchronous: true,388endSelectionState: undefined389});390}391break;392}393}394395private _generateAlternativeId() {396return `${this._notebookSpecificAlternativeId}_` + this.cells.map(cell => cell.handle + ',' + cell.alternativeId).join(';');397}398399override dispose() {400if (this._isDisposed) {401// NotebookEditorModel can be disposed twice, don't fire onWillDispose again402return;403}404405this._isDisposed = true;406this._onWillDispose.fire();407this._undoService.removeElements(this.uri);408409dispose(this._cellListeners.values());410this._cellListeners.clear();411412dispose(this._cells);413this._cells = [];414super.dispose();415}416417pushStackElement() {418// https://github.com/microsoft/vscode/issues/207523419}420421private _getCellIndexByHandle(handle: number) {422return this.cells.findIndex(c => c.handle === handle);423}424425private _getCellIndexWithOutputIdHandleFromEdits(outputId: string, rawEdits: ICellEditOperation[]) {426const edit = rawEdits.find(e => 'outputs' in e && e.outputs.some(o => o.outputId === outputId));427if (edit) {428if ('index' in edit) {429return edit.index;430} else if ('handle' in edit) {431const cellIndex = this._getCellIndexByHandle(edit.handle);432this._assertIndex(cellIndex);433return cellIndex;434}435}436437return -1;438}439440private _getCellIndexWithOutputIdHandle(outputId: string) {441return this.cells.findIndex(c => !!c.outputs.find(o => o.outputId === outputId));442}443444reset(cells: ICellDto2[], metadata: NotebookDocumentMetadata, transientOptions: TransientOptions): void {445this.transientOptions = transientOptions;446const executions = this._notebookExecutionStateService.getCellExecutionsForNotebook(this.uri);447const executingCellHandles = executions.filter(exe => exe.state === NotebookCellExecutionState.Executing).map(exe => exe.cellHandle);448const edits = NotebookTextModel.computeEdits(this, cells, executingCellHandles);449450this.applyEdits(451[452...edits,453{ editType: CellEditType.DocumentMetadata, metadata }454],455true,456undefined, () => undefined,457undefined,458false459);460}461462createSnapshot(options: INotebookSnapshotOptions): NotebookData {463const transientOptions = options.transientOptions ?? this.transientOptions;464const data: NotebookData = {465metadata: filter(this.metadata, key => !transientOptions.transientDocumentMetadata[key]),466cells: [],467};468469let outputSize = 0;470for (const cell of this.cells) {471const cellData: ICellDto2 = {472cellKind: cell.cellKind,473language: cell.language,474mime: cell.mime,475source: cell.getValue(),476outputs: [],477internalMetadata: cell.internalMetadata478};479480if (options.context === SnapshotContext.Backup && options.outputSizeLimit > 0) {481cell.outputs.forEach(output => {482output.outputs.forEach(item => {483outputSize += item.data.byteLength;484});485});486if (outputSize > options.outputSizeLimit) {487throw new Error('Notebook too large to backup');488}489}490491cellData.outputs = !transientOptions.transientOutputs ? cell.outputs : [];492cellData.metadata = filter(cell.metadata, key => !transientOptions.transientCellMetadata[key]);493494data.cells.push(cellData);495}496497return data;498}499500restoreSnapshot(snapshot: NotebookData, transientOptions?: TransientOptions): void {501this.reset(snapshot.cells, snapshot.metadata, transientOptions ?? this.transientOptions);502}503504static computeEdits(model: NotebookTextModel, cells: ICellDto2[], executingHandles: number[] = []): ICellEditOperation[] {505const edits: ICellEditOperation[] = [];506const isExecuting = (cell: NotebookCellTextModel) => executingHandles.includes(cell.handle);507508const commonPrefix = this._commonPrefix(model.cells, model.cells.length, 0, cells, cells.length, 0, isExecuting);509510if (commonPrefix > 0) {511for (let i = 0; i < commonPrefix; i++) {512edits.push(513{514editType: CellEditType.Metadata,515index: i,516metadata: cells[i].metadata ?? {}517},518...this._computeOutputEdit(i, model.cells[i].outputs, cells[i].outputs)519);520}521}522523if (model.cells.length === cells.length && commonPrefix === model.cells.length) {524return edits;525}526527const commonSuffix = this._commonSuffix(model.cells, model.cells.length - commonPrefix, commonPrefix, cells, cells.length - commonPrefix, commonPrefix, isExecuting);528529if (commonSuffix > 0) {530edits.push({ editType: CellEditType.Replace, index: commonPrefix, count: model.cells.length - commonPrefix - commonSuffix, cells: cells.slice(commonPrefix, cells.length - commonSuffix) });531} else if (commonPrefix > 0) {532edits.push({ editType: CellEditType.Replace, index: commonPrefix, count: model.cells.length - commonPrefix, cells: cells.slice(commonPrefix) });533} else {534edits.push({ editType: CellEditType.Replace, index: 0, count: model.cells.length, cells });535}536537if (commonSuffix > 0) {538// has same suffix539for (let i = commonSuffix; i > 0; i--) {540edits.push(541{542editType: CellEditType.Metadata,543index: model.cells.length - i,544metadata: cells[cells.length - i].metadata ?? {}545},546...this._computeOutputEdit(model.cells.length - i, model.cells[model.cells.length - i].outputs, cells[cells.length - i].outputs)547);548}549}550551return edits;552}553554private static _computeOutputEdit(index: number, a: ICellOutput[], b: IOutputDto[]): ICellEditOperation[] {555if (a.length !== b.length) {556return [557{558editType: CellEditType.Output,559index: index,560outputs: b,561append: false562}563];564}565566if (a.length === 0) {567// no output568return [];569}570571// same length572return b.map((output, i) => {573return {574editType: CellEditType.OutputItems,575outputId: a[i].outputId,576items: output.outputs,577append: false578};579});580}581582private static _commonPrefix(a: readonly NotebookCellTextModel[], aLen: number, aDelta: number, b: ICellDto2[], bLen: number, bDelta: number, isExecuting: (cell: NotebookCellTextModel) => boolean): number {583const maxResult = Math.min(aLen, bLen);584let result = 0;585for (let i = 0; i < maxResult && a[aDelta + i].fastEqual(b[bDelta + i], isExecuting(a[aDelta + i])); i++) {586result++;587}588589return result;590}591592private static _commonSuffix(a: readonly NotebookCellTextModel[], aLen: number, aDelta: number, b: ICellDto2[], bLen: number, bDelta: number, isExecuting: (cell: NotebookCellTextModel) => boolean): number {593const maxResult = Math.min(aLen, bLen);594let result = 0;595for (let i = 0; i < maxResult && a[aDelta + aLen - i - 1].fastEqual(b[bDelta + bLen - i - 1], isExecuting(a[aDelta + aLen - i - 1])); i++) {596result++;597}598return result;599}600601private newCellsFromLastEdit = new Set<number>();602private isOnlyEditingMetadataOnNewCells(rawEdits: ICellEditOperation[]): boolean {603for (const edit of rawEdits) {604if (edit.editType === CellEditType.PartialInternalMetadata) {605continue;606}607if (edit.editType !== CellEditType.Metadata && edit.editType !== CellEditType.PartialMetadata) {608return false;609}610611if (('index' in edit) && !this.newCellsFromLastEdit.has(this.cells[edit.index].handle)) {612return false;613}614if ('handle' in edit && !this.newCellsFromLastEdit.has(edit.handle)) {615return false;616}617}618619return true;620}621622applyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: ISelectionState | undefined, endSelectionsComputer: () => ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, computeUndoRedo: boolean): boolean {623this._pauseableEmitter.pause();624try {625this._operationManager.pushStackElement(this._alternativeVersionId, undefined);626627if (computeUndoRedo && this.isOnlyEditingMetadataOnNewCells(rawEdits)) {628if (!this._operationManager.appendPreviousOperation()) {629// we can't append the previous operation, so just don't compute undo/redo630computeUndoRedo = false;631}632} else if (computeUndoRedo) {633this.newCellsFromLastEdit.clear();634}635636try {637this._doApplyEdits(rawEdits, synchronous, computeUndoRedo, beginSelectionState, undoRedoGroup);638return true;639} finally {640if (!this._pauseableEmitter.isEmpty) {641// Update selection and versionId after applying edits.642const endSelections = endSelectionsComputer();643this._increaseVersionId(this._operationManager.isUndoStackEmpty() && !this._pauseableEmitter.isDirtyEvent());644645// Finalize undo element646this._operationManager.pushStackElement(this._alternativeVersionId, endSelections);647648// Broadcast changes649this._pauseableEmitter.fire({ rawEvents: [], versionId: this.versionId, synchronous: synchronous, endSelectionState: endSelections });650}651}652} finally {653this._pauseableEmitter.resume();654}655}656657private _doApplyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined): void {658const editsWithDetails = rawEdits.map((edit, index) => {659let cellIndex: number = -1;660if ('index' in edit) {661cellIndex = edit.index;662} else if ('handle' in edit) {663cellIndex = this._getCellIndexByHandle(edit.handle);664this._assertIndex(cellIndex);665} else if ('outputId' in edit) {666cellIndex = this._getCellIndexWithOutputIdHandle(edit.outputId);667if (this._indexIsInvalid(cellIndex)) {668// The referenced output may have been created in this batch of edits669cellIndex = this._getCellIndexWithOutputIdHandleFromEdits(edit.outputId, rawEdits.slice(0, index));670}671672if (this._indexIsInvalid(cellIndex)) {673// It's possible for an edit to refer to an output which was just cleared, ignore it without throwing674return null;675}676} else if (edit.editType !== CellEditType.DocumentMetadata) {677throw new Error('Invalid cell edit');678}679680return {681edit,682cellIndex,683end:684(edit.editType === CellEditType.DocumentMetadata)685? undefined686: (edit.editType === CellEditType.Replace ? edit.index + edit.count : cellIndex),687originalIndex: index688};689}).filter(isDefined);690691// compress all edits which have no side effects on cell index692const edits = this._mergeCellEdits(editsWithDetails)693.sort((a, b) => {694if (a.end === undefined) {695return -1;696}697698if (b.end === undefined) {699return -1;700}701702return b.end - a.end || b.originalIndex - a.originalIndex;703}).reduce((prev, curr) => {704if (!prev.length) {705// empty706prev.push([curr]);707} else {708const last = prev[prev.length - 1];709const index = last[0].cellIndex;710711if (curr.cellIndex === index) {712last.push(curr);713} else {714prev.push([curr]);715}716}717718return prev;719}, [] as TransformedEdit[][]).map(editsOnSameIndex => {720const replaceEdits: TransformedEdit[] = [];721const otherEdits: TransformedEdit[] = [];722723editsOnSameIndex.forEach(edit => {724if (edit.edit.editType === CellEditType.Replace) {725replaceEdits.push(edit);726} else {727otherEdits.push(edit);728}729});730731return [...otherEdits.reverse(), ...replaceEdits];732});733734const flattenEdits = edits.flat();735736for (const { edit, cellIndex } of flattenEdits) {737switch (edit.editType) {738case CellEditType.Replace:739this._replaceCells(edit.index, edit.count, edit.cells, synchronous, computeUndoRedo, beginSelectionState, undoRedoGroup);740break;741case CellEditType.Output: {742this._assertIndex(cellIndex);743const cell = this._cells[cellIndex];744if (edit.append) {745this._spliceNotebookCellOutputs(cell, { start: cell.outputs.length, deleteCount: 0, newOutputs: edit.outputs.map(op => new NotebookCellOutputTextModel(op)) }, true, computeUndoRedo);746} else {747this._spliceNotebookCellOutputs2(cell, edit.outputs, computeUndoRedo);748}749break;750}751case CellEditType.OutputItems:752{753this._assertIndex(cellIndex);754const cell = this._cells[cellIndex];755if (edit.append) {756this._appendNotebookCellOutputItems(cell, edit.outputId, edit.items);757} else {758this._replaceNotebookCellOutputItems(cell, edit.outputId, edit.items);759}760}761break;762763case CellEditType.Metadata:764this._assertIndex(edit.index);765this._changeCellMetadata(this._cells[edit.index], edit.metadata, computeUndoRedo, beginSelectionState, undoRedoGroup);766break;767case CellEditType.PartialMetadata:768this._assertIndex(cellIndex);769this._changeCellMetadataPartial(this._cells[cellIndex], edit.metadata, computeUndoRedo, beginSelectionState, undoRedoGroup);770break;771case CellEditType.PartialInternalMetadata:772this._assertIndex(cellIndex);773this._changeCellInternalMetadataPartial(this._cells[cellIndex], edit.internalMetadata);774break;775case CellEditType.CellLanguage:776this._assertIndex(edit.index);777this._changeCellLanguage(this._cells[edit.index], edit.language, computeUndoRedo, beginSelectionState, undoRedoGroup);778break;779case CellEditType.DocumentMetadata:780this._updateNotebookCellMetadata(edit.metadata, computeUndoRedo, beginSelectionState, undoRedoGroup);781break;782case CellEditType.Move:783this._moveCellToIdx(edit.index, edit.length, edit.newIdx, synchronous, computeUndoRedo, beginSelectionState, undefined, undoRedoGroup);784break;785}786}787}788789private _mergeCellEdits(rawEdits: TransformedEdit[]): TransformedEdit[] {790const mergedEdits: TransformedEdit[] = [];791792rawEdits.forEach(edit => {793if (mergedEdits.length) {794const last = mergedEdits[mergedEdits.length - 1];795796if (last.edit.editType === CellEditType.Output797&& last.edit.append798&& edit.edit.editType === CellEditType.Output799&& edit.edit.append800&& last.cellIndex === edit.cellIndex801) {802last.edit.outputs = [...last.edit.outputs, ...edit.edit.outputs];803} else if (last.edit.editType === CellEditType.Output804&& !last.edit.append // last cell is not append805&& last.edit.outputs.length === 0 // last cell is clear outputs806&& edit.edit.editType === CellEditType.Output807&& edit.edit.append808&& last.cellIndex === edit.cellIndex809) {810last.edit.append = false;811last.edit.outputs = edit.edit.outputs;812} else {813mergedEdits.push(edit);814}815} else {816mergedEdits.push(edit);817}818});819820return mergedEdits;821}822823private _getDefaultCollapseState(cellDto: ICellDto2): NotebookCellCollapseState | undefined {824const defaultConfig = cellDto.cellKind === CellKind.Code ? this._defaultCollapseConfig?.codeCell : this._defaultCollapseConfig?.markupCell;825return cellDto.collapseState ?? (defaultConfig ?? undefined);826}827828private _replaceCells(index: number, count: number, cellDtos: ICellDto2[], synchronous: boolean, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined): void {829830if (count === 0 && cellDtos.length === 0) {831return;832}833834const oldViewCells = this._cells.slice(0);835const oldSet = new Set();836oldViewCells.forEach(cell => {837oldSet.add(cell.handle);838});839840// prepare remove841for (let i = index; i < Math.min(index + count, this._cells.length); i++) {842const cell = this._cells[i];843this._cellListeners.get(cell.handle)?.dispose();844this._cellListeners.delete(cell.handle);845}846847// prepare add848const cells = cellDtos.map(cellDto => {849const cellHandle = this._cellhandlePool++;850const cellUri = CellUri.generate(this.uri, cellHandle);851const collapseState = this._getDefaultCollapseState(cellDto);852const cell = new NotebookCellTextModel(853cellUri, cellHandle,854cellDto.source, cellDto.language, cellDto.mime, cellDto.cellKind, cellDto.outputs || [], cellDto.metadata, cellDto.internalMetadata, collapseState, this.transientOptions,855this._languageService,856this._modelService.getCreationOptions(cellDto.language, cellUri, false).defaultEOL,857this._languageDetectionService858);859const textModel = this._modelService.getModel(cellUri);860if (textModel && textModel instanceof TextModel) {861cell.textModel = textModel;862cell.language = cellDto.language;863cell.textModel.setValue(cellDto.source);864cell.resetTextBuffer(cell.textModel.getTextBuffer());865}866const dirtyStateListener = cell.onDidChangeContent((e) => {867this._bindCellContentHandler(cell, e);868});869870this.newCellsFromLastEdit.add(cell.handle);871this._cellListeners.set(cell.handle, dirtyStateListener);872this._register(cell);873return cell;874});875876// compute change877const cellsCopy = this._cells.slice(0);878cellsCopy.splice(index, count, ...cells);879const diffs = diff(this._cells, cellsCopy, cell => {880return oldSet.has(cell.handle);881}).map(diff => {882return [diff.start, diff.deleteCount, diff.toInsert] as [number, number, NotebookCellTextModel[]];883});884this._onWillAddRemoveCells.fire({ rawEvent: { kind: NotebookCellsChangeType.ModelChange, changes: diffs } });885886// make change887this._cells = cellsCopy;888889const undoDiff = diffs.map(diff => {890const deletedCells = oldViewCells.slice(diff[0], diff[0] + diff[1]);891892return [diff[0], deletedCells, diff[2]] as [number, NotebookCellTextModel[], NotebookCellTextModel[]];893});894895if (computeUndoRedo) {896this._operationManager.pushEditOperation(new SpliceCellsEdit(this.uri, undoDiff, {897insertCell: (index, cell, endSelections) => { this._insertNewCell(index, [cell], true, endSelections); },898deleteCell: (index, endSelections) => { this._removeCell(index, 1, true, endSelections); },899replaceCell: (index, count, cells, endSelections) => { this._replaceNewCells(index, count, cells, true, endSelections); },900}, undefined, undefined), beginSelectionState, undefined, this._alternativeVersionId, undoRedoGroup);901}902903// should be deferred904this._pauseableEmitter.fire({905rawEvents: [{ kind: NotebookCellsChangeType.ModelChange, changes: diffs, transient: false }],906versionId: this.versionId,907synchronous: synchronous,908endSelectionState: undefined909});910}911912private _increaseVersionId(transient: boolean): void {913this._versionId = this._versionId + 1;914if (!transient) {915this._notebookSpecificAlternativeId = this._versionId;916}917this._alternativeVersionId = this._generateAlternativeId();918}919920private _overwriteAlternativeVersionId(newAlternativeVersionId: string): void {921this._alternativeVersionId = newAlternativeVersionId;922this._notebookSpecificAlternativeId = Number(newAlternativeVersionId.substring(0, newAlternativeVersionId.indexOf('_')));923}924925private _updateNotebookCellMetadata(metadata: NotebookDocumentMetadata, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined) {926const oldMetadata = this.metadata;927const triggerDirtyChange = this._isDocumentMetadataChanged(this.metadata, metadata);928929if (triggerDirtyChange) {930if (computeUndoRedo) {931const that = this;932this._operationManager.pushEditOperation(new class implements IResourceUndoRedoElement {933readonly type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;934get resource() {935return that.uri;936}937readonly label = 'Update Cell Metadata';938readonly code = 'undoredo.textBufferEdit';939undo() {940that._updateNotebookCellMetadata(oldMetadata, false, beginSelectionState, undoRedoGroup);941}942redo() {943that._updateNotebookCellMetadata(metadata, false, beginSelectionState, undoRedoGroup);944}945}(), beginSelectionState, undefined, this._alternativeVersionId, undoRedoGroup);946}947}948949this.metadata = metadata;950this._pauseableEmitter.fire({951rawEvents: [{ kind: NotebookCellsChangeType.ChangeDocumentMetadata, metadata: this.metadata, transient: !triggerDirtyChange }],952versionId: this.versionId,953synchronous: true,954endSelectionState: undefined955});956}957958private _insertNewCell(index: number, cells: NotebookCellTextModel[], synchronous: boolean, endSelections: ISelectionState | undefined): void {959for (let i = 0; i < cells.length; i++) {960const dirtyStateListener = cells[i].onDidChangeContent((e) => {961this._bindCellContentHandler(cells[i], e);962});963964this._cellListeners.set(cells[i].handle, dirtyStateListener);965}966967const changes: NotebookCellTextModelSplice<ICell>[] = [[index, 0, cells]];968this._onWillAddRemoveCells.fire({ rawEvent: { kind: NotebookCellsChangeType.ModelChange, changes } });969this._cells.splice(index, 0, ...cells);970this._pauseableEmitter.fire({971rawEvents: [{ kind: NotebookCellsChangeType.ModelChange, changes, transient: false }],972versionId: this.versionId,973synchronous: synchronous,974endSelectionState: endSelections975});976977return;978}979980private _removeCell(index: number, count: number, synchronous: boolean, endSelections: ISelectionState | undefined) {981for (let i = index; i < index + count; i++) {982const cell = this._cells[i];983this._cellListeners.get(cell.handle)?.dispose();984this._cellListeners.delete(cell.handle);985}986const changes: NotebookCellTextModelSplice<ICell>[] = [[index, count, []]];987this._onWillAddRemoveCells.fire({ rawEvent: { kind: NotebookCellsChangeType.ModelChange, changes } });988this._cells.splice(index, count);989this._pauseableEmitter.fire({990rawEvents: [{ kind: NotebookCellsChangeType.ModelChange, changes, transient: false }],991versionId: this.versionId,992synchronous: synchronous,993endSelectionState: endSelections994});995}996997private _replaceNewCells(index: number, count: number, cells: NotebookCellTextModel[], synchronous: boolean, endSelections: ISelectionState | undefined) {998for (let i = index; i < index + count; i++) {999const cell = this._cells[i];1000this._cellListeners.get(cell.handle)?.dispose();1001this._cellListeners.delete(cell.handle);1002}10031004for (let i = 0; i < cells.length; i++) {1005const dirtyStateListener = cells[i].onDidChangeContent((e) => {1006this._bindCellContentHandler(cells[i], e);1007});10081009this._cellListeners.set(cells[i].handle, dirtyStateListener);1010}10111012const changes: NotebookCellTextModelSplice<ICell>[] = [[index, count, cells]];1013this._onWillAddRemoveCells.fire({ rawEvent: { kind: NotebookCellsChangeType.ModelChange, changes } });1014this._cells.splice(index, count, ...cells);1015this._pauseableEmitter.fire({1016rawEvents: [{ kind: NotebookCellsChangeType.ModelChange, changes, transient: false }],1017versionId: this.versionId,1018synchronous: synchronous,1019endSelectionState: endSelections1020});1021}10221023private _isDocumentMetadataChanged(a: NotebookDocumentMetadata, b: NotebookDocumentMetadata) {1024const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})]);1025for (const key of keys) {1026if (key === 'custom') {1027if (!this._customMetadataEqual(a[key], b[key])1028&&1029!(this.transientOptions.transientDocumentMetadata[key as keyof NotebookDocumentMetadata])1030) {1031return true;1032}1033} else if (1034(a[key as keyof NotebookDocumentMetadata] !== b[key as keyof NotebookDocumentMetadata])1035&&1036!(this.transientOptions.transientDocumentMetadata[key as keyof NotebookDocumentMetadata])1037) {1038return true;1039}1040}10411042return false;1043}10441045private _isCellMetadataChanged(a: NotebookCellMetadata, b: NotebookCellMetadata) {1046const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})]);1047for (const key of keys) {1048if (1049(a[key as keyof NotebookCellMetadata] !== b[key as keyof NotebookCellMetadata])1050&&1051!(this.transientOptions.transientCellMetadata[key as keyof NotebookCellMetadata])1052) {1053return true;1054}1055}10561057return false;1058}10591060private _customMetadataEqual(a: any, b: any) {1061if (!a && !b) {1062// both of them are nullish or undefined1063return true;1064}10651066if (!a || !b) {1067return false;1068}10691070const aProps = Object.getOwnPropertyNames(a);1071const bProps = Object.getOwnPropertyNames(b);10721073if (aProps.length !== bProps.length) {1074return false;1075}10761077for (let i = 0; i < aProps.length; i++) {1078const propName = aProps[i];1079if (a[propName] !== b[propName]) {1080return false;1081}1082}10831084return true;1085}10861087private _changeCellMetadataPartial(cell: NotebookCellTextModel, metadata: NullablePartialNotebookCellMetadata, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined) {1088const newMetadata: NotebookCellMetadata = {1089...cell.metadata1090};1091let k: keyof NullablePartialNotebookCellMetadata;1092for (k in metadata) {1093const value = metadata[k] ?? undefined;1094newMetadata[k] = value as any;1095}10961097return this._changeCellMetadata(cell, newMetadata, computeUndoRedo, beginSelectionState, undoRedoGroup);1098}10991100private _changeCellMetadata(cell: NotebookCellTextModel, metadata: NotebookCellMetadata, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined) {1101const triggerDirtyChange = this._isCellMetadataChanged(cell.metadata, metadata);11021103if (triggerDirtyChange) {1104if (computeUndoRedo) {1105const index = this._cells.indexOf(cell);1106this._operationManager.pushEditOperation(new CellMetadataEdit(this.uri, index, Object.freeze(cell.metadata), Object.freeze(metadata), {1107updateCellMetadata: (index, newMetadata) => {1108const cell = this._cells[index];1109if (!cell) {1110return;1111}1112this._changeCellMetadata(cell, newMetadata, false, beginSelectionState, undoRedoGroup);1113}1114}), beginSelectionState, undefined, this._alternativeVersionId, undoRedoGroup);1115}1116}11171118// should be deferred1119cell.metadata = metadata;1120this._pauseableEmitter.fire({1121rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellMetadata, index: this._cells.indexOf(cell), metadata: cell.metadata, transient: !triggerDirtyChange }],1122versionId: this.versionId,1123synchronous: true,1124endSelectionState: undefined1125});1126}11271128private _changeCellInternalMetadataPartial(cell: NotebookCellTextModel, internalMetadata: NullablePartialNotebookCellInternalMetadata) {1129const newInternalMetadata: NotebookCellInternalMetadata = {1130...cell.internalMetadata1131};1132let k: keyof NotebookCellInternalMetadata;1133for (k in internalMetadata) {1134const value = internalMetadata[k] ?? undefined;1135newInternalMetadata[k] = value as any;1136}11371138cell.internalMetadata = newInternalMetadata;1139this._pauseableEmitter.fire({1140rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellInternalMetadata, index: this._cells.indexOf(cell), internalMetadata: cell.internalMetadata, transient: true }],1141versionId: this.versionId,1142synchronous: true,1143endSelectionState: undefined1144});1145}11461147private _changeCellLanguage(cell: NotebookCellTextModel, languageId: string, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined) {1148if (cell.language === languageId) {1149return;1150}11511152const oldLanguage = cell.language;1153cell.language = languageId;11541155if (computeUndoRedo) {1156const that = this;1157this._operationManager.pushEditOperation(new class implements IResourceUndoRedoElement {1158readonly type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;1159get resource() {1160return that.uri;1161}1162readonly label = 'Update Cell Language';1163readonly code = 'undoredo.textBufferEdit';1164undo() {1165that._changeCellLanguage(cell, oldLanguage, false, beginSelectionState, undoRedoGroup);1166}1167redo() {1168that._changeCellLanguage(cell, languageId, false, beginSelectionState, undoRedoGroup);1169}1170}(), beginSelectionState, undefined, this._alternativeVersionId, undoRedoGroup);1171}11721173this._pauseableEmitter.fire({1174rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellLanguage, index: this._cells.indexOf(cell), language: languageId, transient: false }],1175versionId: this.versionId,1176synchronous: true,1177endSelectionState: undefined1178});1179}11801181private _spliceNotebookCellOutputs2(cell: NotebookCellTextModel, outputs: IOutputDto[], computeUndoRedo: boolean): void {1182if (outputs.length === 0 && cell.outputs.length === 0) {1183return;1184}11851186if (outputs.length <= 1) {1187this._spliceNotebookCellOutputs(cell, { start: 0, deleteCount: cell.outputs.length, newOutputs: outputs.map(op => new NotebookCellOutputTextModel(op)) }, false, computeUndoRedo);1188return;1189}11901191const diff = new LcsDiff(new OutputSequence(cell.outputs), new OutputSequence(outputs));1192const diffResult = diff.ComputeDiff(false);1193const splices: NotebookCellOutputsSplice[] = diffResult.changes.map(change => ({1194start: change.originalStart,1195deleteCount: change.originalLength,1196// create cell output text model only when it's inserted into the notebook document1197newOutputs: outputs.slice(change.modifiedStart, change.modifiedStart + change.modifiedLength).map(op => new NotebookCellOutputTextModel(op))1198}));1199splices.reverse().forEach(splice => {1200this._spliceNotebookCellOutputs(cell, splice, false, computeUndoRedo);1201});1202}12031204private _spliceNotebookCellOutputs(cell: NotebookCellTextModel, splice: NotebookCellOutputsSplice, append: boolean, computeUndoRedo: boolean): void {1205cell.spliceNotebookCellOutputs(splice);1206this._pauseableEmitter.fire({1207rawEvents: [{1208kind: NotebookCellsChangeType.Output,1209index: this._cells.indexOf(cell),1210outputs: cell.outputs.map(output => output.asDto()) ?? [],1211append,1212transient: this.transientOptions.transientOutputs,1213}],1214versionId: this.versionId,1215synchronous: true,1216endSelectionState: undefined1217});1218}12191220private _appendNotebookCellOutputItems(cell: NotebookCellTextModel, outputId: string, items: IOutputItemDto[]) {1221if (cell.changeOutputItems(outputId, true, items)) {1222this._pauseableEmitter.fire({1223rawEvents: [{1224kind: NotebookCellsChangeType.OutputItem,1225index: this._cells.indexOf(cell),1226outputId: outputId,1227outputItems: items,1228append: true,1229transient: this.transientOptions.transientOutputs12301231}],1232versionId: this.versionId,1233synchronous: true,1234endSelectionState: undefined1235});1236}1237}12381239private _replaceNotebookCellOutputItems(cell: NotebookCellTextModel, outputId: string, items: IOutputItemDto[]) {1240if (cell.changeOutputItems(outputId, false, items)) {1241this._pauseableEmitter.fire({1242rawEvents: [{1243kind: NotebookCellsChangeType.OutputItem,1244index: this._cells.indexOf(cell),1245outputId: outputId,1246outputItems: items,1247append: false,1248transient: this.transientOptions.transientOutputs12491250}],1251versionId: this.versionId,1252synchronous: true,1253endSelectionState: undefined1254});1255}1256}12571258private _moveCellToIdx(index: number, length: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean, beforeSelections: ISelectionState | undefined, endSelections: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined): boolean {1259if (pushedToUndoStack) {1260this._operationManager.pushEditOperation(new MoveCellEdit(this.uri, index, length, newIdx, {1261moveCell: (fromIndex: number, length: number, toIndex: number, beforeSelections: ISelectionState | undefined, endSelections: ISelectionState | undefined) => {1262this._moveCellToIdx(fromIndex, length, toIndex, true, false, beforeSelections, endSelections, undoRedoGroup);1263},1264}, beforeSelections, endSelections), beforeSelections, endSelections, this._alternativeVersionId, undoRedoGroup);1265}12661267this._assertIndex(index);1268this._assertIndex(newIdx);12691270const cells = this._cells.splice(index, length);1271this._cells.splice(newIdx, 0, ...cells);1272this._pauseableEmitter.fire({1273rawEvents: [{ kind: NotebookCellsChangeType.Move, index, length, newIdx, cells, transient: false }],1274versionId: this.versionId,1275synchronous: synchronous,1276endSelectionState: endSelections1277});12781279return true;1280}12811282private _assertIndex(index: number) {1283if (this._indexIsInvalid(index)) {1284throw new Error(`model index out of range ${index}`);1285}1286}12871288private _indexIsInvalid(index: number): boolean {1289return index < 0 || index >= this._cells.length;1290}12911292//#region Find1293findNextMatch(searchString: string, searchStart: { cellIndex: number; position: Position }, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, searchEnd?: { cellIndex: number; position: Position }): { cell: NotebookCellTextModel; match: FindMatch } | null {1294// check if search cell index is valid1295this._assertIndex(searchStart.cellIndex);1296const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);1297const searchData = searchParams.parseSearchRequest();12981299if (!searchData) {1300return null;1301}13021303let cellIndex = searchStart.cellIndex;1304let searchStartPosition = searchStart.position;13051306let searchEndCell = this._cells.length;13071308while (cellIndex < searchEndCell) {1309const cell = this._cells[cellIndex];13101311// if we have wrapped back to the point of the initial search cell, we search from beginning to the provided searchEnd position1312const wrapFlag = searchEnd && cellIndex === searchEnd.cellIndex && searchStartPosition.isBefore(searchEnd.position);1313const searchRange = new Range(1314searchStartPosition.lineNumber,1315searchStartPosition.column,1316(wrapFlag) ? searchEnd.position.lineNumber : cell.textBuffer.getLineCount(),1317(wrapFlag) ? searchEnd.position.column : cell.textBuffer.getLineMaxColumn(cell.textBuffer.getLineCount())1318);13191320const result = cell.textBuffer.findMatchesLineByLine(searchRange, searchData, false, 1);1321if (result.length > 0) {1322return { cell, match: result[0] };1323} else if (wrapFlag) { // this means there are no more valid matches in the notebook1324break;1325}13261327// Move to the next cell1328cellIndex++;13291330// wrap if a searchEnd is provided and we are past the end of the notebook1331if (searchEnd && cellIndex >= this._cells.length) {1332cellIndex = 0;1333searchEndCell = searchEnd.cellIndex + 1;1334}13351336searchStartPosition = new Position(1, 1); // Reset position to start of the next cell1337}13381339return null;1340}13411342findMatches(searchString: string, isRegex: boolean, matchCase: boolean, wordSeparators: string | null): { cell: NotebookCellTextModel; matches: FindMatch[] }[] {1343const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);1344const searchData = searchParams.parseSearchRequest();13451346if (!searchData) {1347return [];1348}13491350const results: { cell: NotebookCellTextModel; matches: FindMatch[] }[] = [];1351for (const cell of this._cells) {1352const searchRange = new Range(1, 1, cell.textBuffer.getLineCount(), cell.textBuffer.getLineMaxColumn(cell.textBuffer.getLineCount()));1353const matches = cell.textBuffer.findMatchesLineByLine(searchRange, searchData, false, 1000);13541355if (matches.length > 0) {1356results.push({ cell, matches: matches });1357}1358}13591360return results;1361}1362//#endregion1363}13641365class OutputSequence implements ISequence {1366constructor(readonly outputs: IOutputDto[]) {1367}13681369getElements(): Int32Array | number[] | string[] {1370return this.outputs.map(output => {1371return hash(output.outputs.map(output => ({1372mime: output.mime,1373data: output.data1374})));1375});1376}13771378}137913801381