Merge from vscode e3c4990c67c40213af168300d1cfeb71d680f877 (#16569)

This commit is contained in:
Cory Rivera
2021-08-25 16:28:29 -07:00
committed by GitHub
parent ab1112bfb3
commit cb7b7da0a4
1752 changed files with 59525 additions and 33878 deletions

View File

@@ -3,54 +3,31 @@
* Licensed under the Source EULA. 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 { CancellationToken } 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 { 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 { 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 { ExtHostNotebookShape, IMainContext, IModelAddedData, INotebookCellStatusBarListDto, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, 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, IImmediateCellEditOperation, INotebookExclusiveDocumentFilter, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, NullablePartialNotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookExclusiveDocumentFilter, INotebookContributionData, NotebookCellsChangeType, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import type * as vscode from 'vscode';
import { ExtHostCell, ExtHostNotebookDocument } from './extHostNotebookDocument';
import { ExtHostNotebookEditor } from './extHostNotebookEditor';
export class NotebookEditorDecorationType {
private static readonly _Keys = new IdGenerator('NotebookEditorDecorationType');
readonly value: vscode.NotebookEditorDecorationType;
constructor(proxy: MainThreadNotebookEditorsShape, options: vscode.NotebookDecorationRenderOptions) {
const key = NotebookEditorDecorationType._Keys.nextId();
proxy.$registerNotebookEditorDecorationType(key, typeConverters.NotebookDecorationRenderOptions.from(options));
this.value = {
key,
dispose() {
proxy.$removeNotebookEditorDecorationType(key);
}
};
}
}
type NotebookContentProviderData = {
readonly provider: vscode.NotebookContentProvider;
readonly extension: IExtensionDescription;
@@ -68,12 +45,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
private readonly _documents = new ResourceMap<ExtHostNotebookDocument>();
private readonly _editors = new Map<string, ExtHostNotebookEditor>();
private readonly _commandsConverter: CommandsConverter;
private readonly _onDidChangeNotebookEditorSelection = new Emitter<vscode.NotebookEditorSelectionChangeEvent>();
readonly onDidChangeNotebookEditorSelection = this._onDidChangeNotebookEditorSelection.event;
private readonly _onDidChangeNotebookEditorVisibleRanges = new Emitter<vscode.NotebookEditorVisibleRangesChangeEvent>();
readonly onDidChangeNotebookEditorVisibleRanges = this._onDidChangeNotebookEditorVisibleRanges.event;
private readonly _onDidChangeNotebookDocumentMetadata = new Emitter<vscode.NotebookDocumentMetadataChangeEvent>();
readonly onDidChangeNotebookDocumentMetadata = this._onDidChangeNotebookDocumentMetadata.event;
private readonly _onDidChangeNotebookCells = new Emitter<vscode.NotebookCellsChangeEvent>();
readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event;
private readonly _onDidChangeCellOutputs = new Emitter<vscode.NotebookCellOutputsChangeEvent>();
@@ -98,13 +70,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
onDidOpenNotebookDocument: Event<vscode.NotebookDocument> = this._onDidOpenNotebookDocument.event;
private _onDidCloseNotebookDocument = new Emitter<vscode.NotebookDocument>();
onDidCloseNotebookDocument: Event<vscode.NotebookDocument> = this._onDidCloseNotebookDocument.event;
private _onDidSaveNotebookDocument = new Emitter<vscode.NotebookDocument>();
onDidSaveNotebookDocument: Event<vscode.NotebookDocument> = this._onDidSaveNotebookDocument.event;
private _onDidChangeVisibleNotebookEditors = new Emitter<vscode.NotebookEditor[]>();
onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event;
private _activeExecutions = new ResourceMap<NotebookCellExecutionTask>();
private _statusBarCache = new Cache<IDisposable>('NotebookCellStatusBarCache');
constructor(
@@ -112,7 +81,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
commands: ExtHostCommands,
private _textDocumentsAndEditors: ExtHostDocumentsAndEditors,
private _textDocuments: ExtHostDocuments,
private readonly logService: ILogService,
private readonly _extensionStoragePaths: IExtensionStoragePaths,
) {
this._notebookProxy = mainContext.getProxy(MainContext.MainThreadNotebook);
@@ -138,8 +106,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
});
}
getEditorById(editorId: string): ExtHostNotebookEditor | undefined {
return this._editors.get(editorId);
getEditorById(editorId: string): ExtHostNotebookEditor {
const editor = this._editors.get(editorId);
if (!editor) {
throw new Error(`unknown text editor: ${editorId}. known editors: ${[...this._editors.keys()]} `);
}
return editor;
}
getIdByEditor(editor: vscode.NotebookEditor): string | undefined {
@@ -155,13 +127,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
return [...this._documents.values()];
}
lookupNotebookDocument(uri: URI): ExtHostNotebookDocument | undefined {
return this._documents.get(uri);
}
private _getNotebookDocument(uri: URI): ExtHostNotebookDocument {
getNotebookDocument(uri: URI, relaxed: true): ExtHostNotebookDocument | undefined;
getNotebookDocument(uri: URI): ExtHostNotebookDocument;
getNotebookDocument(uri: URI, relaxed?: true): ExtHostNotebookDocument | undefined {
const result = this._documents.get(uri);
if (!result) {
if (!result && !relaxed) {
throw new Error(`NO notebook document for '${uri}'`);
}
return result;
@@ -179,13 +149,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
extension: IExtensionDescription,
viewType: string,
provider: vscode.NotebookContentProvider,
options?: vscode.NotebookDocumentContentOptions & {
viewOptions?: {
displayName: string;
filenamePattern: (vscode.GlobPattern | { include: vscode.GlobPattern; exclude: vscode.GlobPattern })[];
exclusive?: boolean;
};
}
options?: vscode.NotebookDocumentContentOptions,
registration?: vscode.NotebookRegistrationData
): vscode.Disposable {
if (isFalsyOrWhitespace(viewType)) {
throw new Error(`viewType cannot be empty or just whitespace`);
@@ -196,7 +161,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
this._notebookContentProviders.set(viewType, { extension, provider });
let listener: IDisposable | undefined;
if (provider.onDidChangeNotebookContentOptions) {
listener = provider.onDidChangeNotebookContentOptions(() => {
@@ -205,21 +169,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
});
}
const viewOptionsFilenamePattern = options?.viewOptions?.filenamePattern
.map(pattern => typeConverters.NotebookExclusiveDocumentPattern.from(pattern))
.filter(pattern => pattern !== undefined) as (string | IRelativePattern | INotebookExclusiveDocumentFilter)[];
if (options?.viewOptions?.filenamePattern && !viewOptionsFilenamePattern) {
console.warn(`Notebook content provider view options file name pattern is invalid ${options?.viewOptions?.filenamePattern}`);
}
const internalOptions = typeConverters.NotebookDocumentContentOptions.from(options);
this._notebookProxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, {
transientOutputs: internalOptions.transientOutputs,
transientCellMetadata: internalOptions.transientCellMetadata,
transientDocumentMetadata: internalOptions.transientDocumentMetadata,
viewOptions: options?.viewOptions && viewOptionsFilenamePattern ? { displayName: options.viewOptions.displayName, filenamePattern: viewOptionsFilenamePattern, exclusive: options.viewOptions.exclusive || false } : undefined
});
this._notebookProxy.$registerNotebookProvider(
{ id: extension.identifier, location: extension.extensionLocation },
viewType,
typeConverters.NotebookDocumentContentOptions.from(options),
ExtHostNotebookController._convertNotebookRegistrationData(extension, registration)
);
return new extHostTypes.Disposable(() => {
listener?.dispose();
@@ -228,13 +183,33 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
});
}
registerNotebookCellStatusBarItemProvider(extension: IExtensionDescription, selector: vscode.NotebookSelector, provider: vscode.NotebookCellStatusBarItemProvider) {
private static _convertNotebookRegistrationData(extension: IExtensionDescription, registration: vscode.NotebookRegistrationData | undefined): INotebookContributionData | undefined {
if (!registration) {
return undefined; // {{SQL CARBON EDIT}} Strict nulls
}
const viewOptionsFilenamePattern = registration.filenamePattern
.map(pattern => typeConverters.NotebookExclusiveDocumentPattern.from(pattern))
.filter(pattern => pattern !== undefined) as (string | IRelativePattern | INotebookExclusiveDocumentFilter)[];
if (registration.filenamePattern && !viewOptionsFilenamePattern) {
console.warn(`Notebook content provider view options file name pattern is invalid ${registration.filenamePattern}`);
return undefined;
}
return {
extension: extension.identifier,
providerDisplayName: extension.displayName || extension.name,
displayName: registration.displayName,
filenamePattern: viewOptionsFilenamePattern,
exclusive: registration.exclusive || false
};
}
registerNotebookCellStatusBarItemProvider(extension: IExtensionDescription, notebookType: string, provider: vscode.NotebookCellStatusBarItemProvider) {
const handle = ExtHostNotebookController._notebookStatusBarItemProviderHandlePool++;
const eventHandle = typeof provider.onDidChangeCellStatusBarItems === 'function' ? ExtHostNotebookController._notebookStatusBarItemProviderHandlePool++ : undefined;
this._notebookStatusBarItemProviders.set(handle, provider);
this._notebookProxy.$registerNotebookCellStatusBarItemProvider(handle, eventHandle, selector);
this._notebookProxy.$registerNotebookCellStatusBarItemProvider(handle, eventHandle, notebookType);
let subscription: vscode.Disposable | undefined;
if (eventHandle !== undefined) {
@@ -250,8 +225,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
});
}
createNotebookEditorDecorationType(options: vscode.NotebookDecorationRenderOptions): vscode.NotebookEditorDecorationType {
return new NotebookEditorDecorationType(this._notebookEditorsProxy, options).value;
async createNotebookDocument(options: { viewType: string, content?: vscode.NotebookData }): Promise<URI> {
const canonicalUri = await this._notebookDocumentsProxy.$tryCreateNotebook({
viewType: options.viewType,
content: options.content && typeConverters.NotebookData.from(options.content)
});
return URI.revive(canonicalUri);
}
async openNotebookDocument(uri: URI): Promise<vscode.NotebookDocument> {
@@ -259,7 +238,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
if (cached) {
return cached.apiNotebook;
}
const canonicalUri = await this._notebookDocumentsProxy.$tryOpenDocument(uri);
const canonicalUri = await this._notebookDocumentsProxy.$tryOpenNotebook(uri);
const document = this._documents.get(URI.revive(canonicalUri));
return assertIsDefined(document?.apiNotebook);
}
@@ -285,7 +264,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
};
}
const editorId = await this._notebookEditorsProxy.$tryShowNotebookDocument(notebookOrUri.uri, notebookOrUri.viewType, resolvedOptions);
const editorId = await this._notebookEditorsProxy.$tryShowNotebookDocument(notebookOrUri.uri, notebookOrUri.notebookType, resolvedOptions);
const editor = editorId && this._editors.get(editorId)?.apiEditor;
if (editor) {
@@ -293,9 +272,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
}
if (editorId) {
throw new Error(`Could NOT open editor for "${notebookOrUri.toString()}" because another editor opened in the meantime.`);
throw new Error(`Could NOT open editor for "${notebookOrUri.uri.toString()}" because another editor opened in the meantime.`);
} else {
throw new Error(`Could NOT open editor for "${notebookOrUri.toString()}".`);
throw new Error(`Could NOT open editor for "${notebookOrUri.uri.toString()}".`);
}
}
@@ -319,7 +298,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
const disposables = new DisposableStore();
const cacheId = this._statusBarCache.add([disposables]);
const items = (result && result.map(item => typeConverters.NotebookStatusBarItem.from(item, this._commandsConverter, disposables))) ?? undefined;
const resultArr = Array.isArray(result) ? result : [result];
const items = resultArr.map(item => typeConverters.NotebookStatusBarItem.from(item, this._commandsConverter, disposables));
return {
cacheId,
items
@@ -335,18 +315,18 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
private _handlePool = 0;
private readonly _notebookSerializer = new Map<number, vscode.NotebookSerializer>();
registerNotebookSerializer(extension: IExtensionDescription, viewType: string, serializer: vscode.NotebookSerializer, options?: vscode.NotebookDocumentContentOptions): vscode.Disposable {
registerNotebookSerializer(extension: IExtensionDescription, viewType: string, serializer: vscode.NotebookSerializer, options?: vscode.NotebookDocumentContentOptions, registration?: vscode.NotebookRegistrationData): vscode.Disposable {
if (isFalsyOrWhitespace(viewType)) {
throw new Error(`viewType cannot be empty or just whitespace`);
}
const handle = this._handlePool++;
this._notebookSerializer.set(handle, serializer);
const internalOptions = typeConverters.NotebookDocumentContentOptions.from(options);
this._notebookProxy.$registerNotebookSerializer(
handle,
{ id: extension.identifier, location: extension.extensionLocation, description: extension.description },
{ id: extension.identifier, location: extension.extensionLocation },
viewType,
internalOptions
typeConverters.NotebookDocumentContentOptions.from(options),
ExtHostNotebookController._convertNotebookRegistrationData(extension, registration)
);
return toDisposable(() => {
this._notebookProxy.$unregisterNotebookSerializer(handle);
@@ -359,10 +339,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
throw new Error('NO serializer found');
}
const data = await serializer.deserializeNotebook(bytes.buffer, token);
return {
metadata: typeConverters.NotebookDocumentMetadata.from(data.metadata),
cells: data.cells.map(typeConverters.NotebookCellData.from),
};
return typeConverters.NotebookData.from(data);
}
async $notebookToData(handle: number, data: NotebookDataDto, token: CancellationToken): Promise<VSBuffer> {
@@ -370,38 +347,30 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
if (!serializer) {
throw new Error('NO serializer found');
}
const bytes = await serializer.serializeNotebook({
metadata: typeConverters.NotebookDocumentMetadata.to(data.metadata),
cells: data.cells.map(typeConverters.NotebookCellData.to)
}, token);
const bytes = await serializer.serializeNotebook(typeConverters.NotebookData.to(data), token);
return VSBuffer.wrap(bytes);
}
cancelOneNotebookCellExecution(cell: ExtHostCell): void {
const execution = this._activeExecutions.get(cell.uri);
execution?.cancel();
}
// --- open, save, saveAs, backup
async $openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<NotebookDataDto> {
const { provider } = this._getProviderData(viewType);
const data = await provider.openNotebook(URI.revive(uri), { backupId, untitledDocumentData: untitledDocumentData?.buffer }, token);
return {
metadata: typeConverters.NotebookDocumentMetadata.from(data.metadata),
metadata: data.metadata ?? Object.create(null),
cells: data.cells.map(typeConverters.NotebookCellData.from),
};
}
async $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean> {
const document = this._getNotebookDocument(URI.revive(uri));
const document = this.getNotebookDocument(URI.revive(uri));
const { provider } = this._getProviderData(viewType);
await provider.saveNotebook(document.apiNotebook, token);
return true;
}
async $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean> {
const document = this._getNotebookDocument(URI.revive(uri));
const document = this.getNotebookDocument(URI.revive(uri));
const { provider } = this._getProviderData(viewType);
await provider.saveNotebookAs(URI.revive(target), document.apiNotebook, token);
return true;
@@ -410,7 +379,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
private _backupIdPool: number = 0;
async $backupNotebook(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string> {
const document = this._getNotebookDocument(URI.revive(uri));
const document = this.getNotebookDocument(URI.revive(uri));
const provider = this._getProviderData(viewType);
const storagePath = this._extensionStoragePaths.workspaceValue(provider.extension) ?? this._extensionStoragePaths.globalValue(provider.extension);
@@ -422,70 +391,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
return backup.id;
}
$acceptModelChanged(uri: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void {
const document = this._getNotebookDocument(URI.revive(uri));
document.acceptModelChanged(event, isDirty);
}
$acceptDirtyStateChanged(uri: UriComponents, isDirty: boolean): void {
const document = this._getNotebookDocument(URI.revive(uri));
document.acceptModelChanged({ rawEvents: [], versionId: document.apiNotebook.version }, isDirty);
}
$acceptModelSaved(uri: UriComponents): void {
const document = this._getNotebookDocument(URI.revive(uri));
this._onDidSaveNotebookDocument.fire(document.apiNotebook);
}
$acceptEditorPropertiesChanged(id: string, data: INotebookEditorPropertiesChangeData): void {
this.logService.debug('ExtHostNotebook#$acceptEditorPropertiesChanged', id, data);
const editor = this._editors.get(id);
if (!editor) {
throw new Error(`unknown text editor: ${id}. known editors: ${[...this._editors.keys()]} `);
}
// ONE: make all state updates
if (data.visibleRanges) {
editor._acceptVisibleRanges(data.visibleRanges.ranges.map(typeConverters.NotebookRange.to));
}
if (data.selections) {
editor._acceptSelections(data.selections.selections.map(typeConverters.NotebookRange.to));
}
// TWO: send all events after states have been updated
if (data.visibleRanges) {
this._onDidChangeNotebookEditorVisibleRanges.fire({
notebookEditor: editor.apiEditor,
visibleRanges: editor.apiEditor.visibleRanges
});
}
if (data.selections) {
this._onDidChangeNotebookEditorSelection.fire(Object.freeze({
notebookEditor: editor.apiEditor,
selections: editor.apiEditor.selections
}));
}
}
$acceptEditorViewColumns(data: INotebookEditorViewColumnInfo): void {
for (const id in data) {
const editor = this._editors.get(id);
if (!editor) {
throw new Error(`unknown text editor: ${id}. known editors: ${[...this._editors.keys()]} `);
}
editor._acceptViewColumn(typeConverters.ViewColumn.to(data[id]));
}
}
$acceptDocumentPropertiesChanged(uri: UriComponents, data: INotebookDocumentPropertiesChangeData): void {
this.logService.debug('ExtHostNotebook#$acceptDocumentPropertiesChanged', uri.path, data);
const document = this._getNotebookDocument(URI.revive(uri));
document.acceptDocumentPropertiesChanged(data);
if (data.metadata) {
this._onDidChangeNotebookDocumentMetadata.fire({ document: document.apiNotebook });
}
}
private _createExtHostEditor(document: ExtHostNotebookDocument, editorId: string, data: INotebookEditorAddData) {
@@ -559,7 +464,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
}
},
viewType,
modelData.metadata ? typeConverters.NotebookDocumentMetadata.to(modelData.metadata) : new extHostTypes.NotebookDocumentMetadata(),
modelData.metadata ?? Object.create({}),
uri,
);
@@ -639,213 +544,4 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
this._onDidChangeActiveNotebookEditor.fire(this._activeNotebookEditor?.apiEditor);
}
}
createNotebookCellExecution(docUri: vscode.Uri, index: number, kernelId: string): vscode.NotebookCellExecutionTask | undefined {
const document = this.lookupNotebookDocument(docUri);
if (!document) {
throw new Error(`Invalid uri: ${docUri} `);
}
const cell = document.getCellFromIndex(index);
if (!cell) {
throw new Error(`Invalid cell index: ${docUri}, ${index} `);
}
// TODO@roblou also validate kernelId, once kernel has moved from editor to document
if (this._activeExecutions.has(cell.uri)) {
throw new Error(`duplicate execution for ${cell.uri}`);
}
const execution = new NotebookCellExecutionTask(docUri, document, cell, this._notebookDocumentsProxy);
this._activeExecutions.set(cell.uri, execution);
const listener = execution.onDidChangeState(() => {
if (execution.state === NotebookCellExecutionTaskState.Resolved) {
execution.dispose();
listener.dispose();
this._activeExecutions.delete(cell.uri);
}
});
return execution.asApiObject();
}
}
enum NotebookCellExecutionTaskState {
Init,
Started,
Resolved
}
class NotebookCellExecutionTask extends Disposable {
private _onDidChangeState = new Emitter<void>();
readonly onDidChangeState = this._onDidChangeState.event;
private _state = NotebookCellExecutionTaskState.Init;
get state(): NotebookCellExecutionTaskState { return this._state; }
private readonly _tokenSource = this._register(new CancellationTokenSource());
private readonly _collector: TimeoutBasedCollector<IImmediateCellEditOperation>;
private _executionOrder: number | undefined;
constructor(
private readonly _uri: vscode.Uri,
private readonly _document: ExtHostNotebookDocument,
private readonly _cell: ExtHostCell,
private readonly _proxy: MainThreadNotebookDocumentsShape) {
super();
this._collector = new TimeoutBasedCollector(10, edits => this.applyEdits(edits));
this._executionOrder = _cell.internalMetadata.executionOrder;
this.mixinMetadata({
runState: extHostTypes.NotebookCellExecutionState.Pending,
executionOrder: null
});
}
cancel(): void {
this._tokenSource.cancel();
}
private async applyEditSoon(edit: IImmediateCellEditOperation): Promise<void> {
await this._collector.addItem(edit);
}
private async applyEdits(edits: IImmediateCellEditOperation[]): Promise<void> {
return this._proxy.$applyEdits(this._uri, edits, false);
}
private verifyStateForOutput() {
if (this._state === NotebookCellExecutionTaskState.Init) {
throw new Error('Must call start before modifying cell output');
}
if (this._state === NotebookCellExecutionTaskState.Resolved) {
throw new Error('Cannot modify cell output after calling resolve');
}
}
private mixinMetadata(mixinMetadata: NullablePartialNotebookCellMetadata) {
const edit: IImmediateCellEditOperation = { editType: CellEditType.PartialMetadata, handle: this._cell.handle, metadata: mixinMetadata };
this.applyEdits([edit]);
}
private cellIndexToHandle(cellIndex: number | undefined): number | undefined {
const cell = typeof cellIndex === 'number' ? this._document.getCellFromIndex(cellIndex) : this._cell;
if (!cell) {
return undefined; // {{SQL CARBON EDIT}} Strict null
}
return cell.handle;
}
asApiObject(): vscode.NotebookCellExecutionTask {
const that = this;
return Object.freeze(<vscode.NotebookCellExecutionTask>{
get document() { return that._document.apiNotebook; },
get cell() { return that._cell.apiCell; },
get executionOrder() { return that._executionOrder; },
set executionOrder(v: number | undefined) {
that._executionOrder = v;
that.mixinMetadata({
executionOrder: v
});
},
start(context?: vscode.NotebookCellExecuteStartContext): void {
if (that._state === NotebookCellExecutionTaskState.Resolved || that._state === NotebookCellExecutionTaskState.Started) {
throw new Error('Cannot call start again');
}
that._state = NotebookCellExecutionTaskState.Started;
that._onDidChangeState.fire();
that.mixinMetadata({
runState: extHostTypes.NotebookCellExecutionState.Executing,
runStartTime: context?.startTime ?? null
});
},
end(result?: vscode.NotebookCellExecuteEndContext): void {
if (that._state === NotebookCellExecutionTaskState.Resolved) {
throw new Error('Cannot call resolve twice');
}
that._state = NotebookCellExecutionTaskState.Resolved;
that._onDidChangeState.fire();
that.mixinMetadata({
runState: extHostTypes.NotebookCellExecutionState.Idle,
lastRunSuccess: result?.success ?? null,
runEndTime: result?.endTime ?? null,
});
},
clearOutput(cellIndex?: number): Thenable<void> {
that.verifyStateForOutput();
return this.replaceOutput([], cellIndex);
},
async appendOutput(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cellIndex?: number): Promise<void> {
that.verifyStateForOutput();
const handle = that.cellIndexToHandle(cellIndex);
if (typeof handle !== 'number') {
return;
}
outputs = Array.isArray(outputs) ? outputs : [outputs];
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<void> {
that.verifyStateForOutput();
const handle = that.cellIndexToHandle(cellIndex);
if (typeof handle !== 'number') {
return;
}
outputs = Array.isArray(outputs) ? outputs : [outputs];
return that.applyEditSoon({ editType: CellEditType.Output, handle, outputs: outputs.map(typeConverters.NotebookCellOutput.from) });
},
async appendOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], outputId: string): Promise<void> {
that.verifyStateForOutput();
items = Array.isArray(items) ? items : [items];
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<void> {
that.verifyStateForOutput();
items = Array.isArray(items) ? items : [items];
return that.applyEditSoon({ editType: CellEditType.OutputItems, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId });
},
token: that._tokenSource.token
});
}
}
class TimeoutBasedCollector<T> {
private batch: T[] = [];
private waitPromise: Promise<void> | undefined;
constructor(
private readonly delay: number,
private readonly callback: (items: T[]) => Promise<void>) { }
addItem(item: T): Promise<void> {
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;
}
}