diff --git a/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts b/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts index 28e3a5937c4..d2bf4a13f27 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts @@ -137,7 +137,7 @@ export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsS async $applyEdits(resource: UriComponents, cellEdits: IImmediateCellEditOperation[], computeUndoRedo = true): Promise { const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); if (!textModel) { - throw new Error(`Can't apply edits to unknown notebook model: ${resource}`); + throw new Error(`Can't apply edits to unknown notebook model: ${URI.revive(resource).toString()}`); } try { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 672776d70aa..116b80d14c0 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -3,31 +3,32 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { timeout } from 'vs/base/common/async'; +import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; +import { IRelativePattern } from 'vs/base/common/glob'; +import { hash } from 'vs/base/common/hash'; +import { IdGenerator } from 'vs/base/common/idGenerator'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { assertIsDefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ExtHostNotebookShape, IMainContext, IModelAddedData, INotebookCellStatusBarListDto, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookEditorPropertiesChangeData, INotebookEditorViewColumnInfo, MainContext, MainThreadNotebookDocumentsShape, MainThreadNotebookEditorsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { ILogService } from 'vs/platform/log/common/log'; +import { Cache } from 'vs/workbench/api/common/cache'; +import { ExtHostNotebookShape, IMainContext, IModelAddedData, INotebookCellStatusBarListDto, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookEditorPropertiesChangeData, INotebookEditorViewColumnInfo, MainContext, MainThreadNotebookDocumentsShape, MainThreadNotebookEditorsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { CellEditType, INotebookExclusiveDocumentFilter, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, NullablePartialNotebookCellMetadata, IImmediateCellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, IImmediateCellEditOperation, INotebookExclusiveDocumentFilter, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, NullablePartialNotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import type * as vscode from 'vscode'; -import { ResourceMap } from 'vs/base/common/map'; import { ExtHostCell, ExtHostNotebookDocument } from './extHostNotebookDocument'; import { ExtHostNotebookEditor } from './extHostNotebookEditor'; -import { IdGenerator } from 'vs/base/common/idGenerator'; -import { IRelativePattern } from 'vs/base/common/glob'; -import { assertIsDefined } from 'vs/base/common/types'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { hash } from 'vs/base/common/hash'; -import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { Cache } from 'vs/workbench/api/common/cache'; -import { isFalsyOrWhitespace } from 'vs/base/common/strings'; export class NotebookEditorDecorationType { @@ -681,7 +682,9 @@ class NotebookCellExecutionTask extends Disposable { private _state = NotebookCellExecutionTaskState.Init; get state(): NotebookCellExecutionTaskState { return this._state; } - private readonly _tokenSource: CancellationTokenSource; + private readonly _tokenSource = this._register(new CancellationTokenSource()); + + private readonly _collector: TimeoutBasedCollector; private _executionOrder: number | undefined; @@ -691,7 +694,8 @@ class NotebookCellExecutionTask extends Disposable { private readonly _cell: ExtHostCell, private readonly _proxy: MainThreadNotebookDocumentsShape) { super(); - this._tokenSource = this._register(new CancellationTokenSource()); + + this._collector = new TimeoutBasedCollector(10, edits => this.applyEdits(edits)); this._executionOrder = _cell.internalMetadata.executionOrder; this.mixinMetadata({ @@ -704,6 +708,10 @@ class NotebookCellExecutionTask extends Disposable { this._tokenSource.cancel(); } + private async applyEditSoon(edit: IImmediateCellEditOperation): Promise { + await this._collector.addItem(edit); + } + private async applyEdits(edits: IImmediateCellEditOperation[]): Promise { return this._proxy.$applyEdits(this._uri, edits, false); } @@ -719,10 +727,8 @@ class NotebookCellExecutionTask extends Disposable { } private mixinMetadata(mixinMetadata: NullablePartialNotebookCellMetadata) { - const edits: IImmediateCellEditOperation[] = [ - { editType: CellEditType.PartialMetadata, handle: this._cell.handle, metadata: mixinMetadata } - ]; - this.applyEdits(edits); + const edit: IImmediateCellEditOperation = { editType: CellEditType.PartialMetadata, handle: this._cell.handle, metadata: mixinMetadata }; + this.applyEdits([edit]); } private cellIndexToHandle(cellIndex: number | undefined): number | undefined { @@ -790,7 +796,7 @@ class NotebookCellExecutionTask extends Disposable { } outputs = Array.isArray(outputs) ? outputs : [outputs]; - return that.applyEdits([{ editType: CellEditType.Output, handle, append: true, outputs: outputs.map(typeConverters.NotebookCellOutput.from) }]); + return that.applyEditSoon({ editType: CellEditType.Output, handle, append: true, outputs: outputs.map(typeConverters.NotebookCellOutput.from) }); }, async replaceOutput(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cellIndex?: number): Promise { @@ -801,22 +807,45 @@ class NotebookCellExecutionTask extends Disposable { } outputs = Array.isArray(outputs) ? outputs : [outputs]; - return that.applyEdits([{ editType: CellEditType.Output, handle, outputs: outputs.map(typeConverters.NotebookCellOutput.from) }]); + return that.applyEditSoon({ editType: CellEditType.Output, handle, outputs: outputs.map(typeConverters.NotebookCellOutput.from) }); }, async appendOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], outputId: string): Promise { that.verifyStateForOutput(); items = Array.isArray(items) ? items : [items]; - return that.applyEdits([{ editType: CellEditType.OutputItems, append: true, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId }]); + return that.applyEditSoon({ editType: CellEditType.OutputItems, append: true, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId }); }, async replaceOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], outputId: string): Promise { that.verifyStateForOutput(); items = Array.isArray(items) ? items : [items]; - return that.applyEdits([{ editType: CellEditType.OutputItems, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId }]); + return that.applyEditSoon({ editType: CellEditType.OutputItems, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId }); }, token: that._tokenSource.token }); } } + +class TimeoutBasedCollector { + private batch: T[] = []; + private waitPromise: Promise | undefined; + + constructor( + private readonly delay: number, + private readonly callback: (items: T[]) => Promise) { } + + addItem(item: T): Promise { + this.batch.push(item); + if (!this.waitPromise) { + this.waitPromise = timeout(this.delay).then(() => { + this.waitPromise = undefined; + const batch = this.batch; + this.batch = []; + return this.callback(batch); + }); + } + + return this.waitPromise; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController.ts index dfe54df10c7..a047dde47df 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController.ts @@ -128,7 +128,7 @@ class ExecutionStateCellStatusBarHelper extends Disposable { alignment: CellStatusbarAlignment.Left, priority: Number.MAX_SAFE_INTEGER }; - } else if (runState === NotebookCellExecutionState.Idle && !lastRunSuccess) { + } else if (runState === NotebookCellExecutionState.Idle && lastRunSuccess === false) { return { text: '$(notebook-state-error)', color: themeColorFromId(cellStatusIconError),