diff --git a/src/vs/workbench/api/browser/mainThreadNotebookDto.ts b/src/vs/workbench/api/browser/mainThreadNotebookDto.ts index 5d582e16f86..d63c6412198 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookDto.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookDto.ts @@ -6,7 +6,7 @@ import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { CellExecutionUpdateType, ICellExecuteUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; +import { CellExecutionUpdateType, ICellExecuteUpdate, ICellExecutionComplete } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; export namespace NotebookDto { @@ -112,6 +112,10 @@ export namespace NotebookDto { } } + export function fromCellExecuteCompleteDto(data: extHostProtocol.ICellExecutionCompleteDto): ICellExecutionComplete { + return data; + } + export function fromCellEditOperationDto(edit: extHostProtocol.ICellEditOperationDto): notebookCommon.ICellEditOperation { if (edit.editType === notebookCommon.CellEditType.Replace) { return { diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index b79261dc74a..71aa6d1b0a4 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -6,7 +6,7 @@ import { flatten, groupBy, isNonEmptyArray } from 'vs/base/common/arrays'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { combinedDisposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { combinedDisposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -14,10 +14,11 @@ import { NotebookDto } from 'vs/workbench/api/browser/mainThreadNotebookDto'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; -import { INotebookCellExecution, INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; +import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookKernel, INotebookKernelChangeEvent, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier'; -import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, IExtHostContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, IExtHostContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol'; abstract class MainThreadKernel implements INotebookKernel { @@ -106,7 +107,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape private readonly _kernels = new Map(); private readonly _proxy: ExtHostNotebookKernelsShape; - private readonly _executions = new Map(); + private readonly _executions = new Set(); constructor( extHostContext: IExtHostContext, @@ -121,6 +122,16 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape notebookEditorService.listNotebookEditors().forEach(this._onEditorAdd, this); notebookEditorService.onDidAddNotebookEditor(this._onEditorAdd, this, this._disposables); notebookEditorService.onDidRemoveNotebookEditor(this._onEditorRemove, this, this._disposables); + + this._disposables.add(toDisposable(() => { + // EH shut down, complete all executions started by this EH + this._executions.forEach(e => { + const uri = CellUri.parse(URI.parse(e)); + if (uri) { + this._notebookExecutionService.completeNotebookCellExecution(uri.notebook, uri.handle, { }); + } + }); + })); } dispose(): void { @@ -235,30 +246,34 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape // --- execution - $addExecution(handle: number, uri: UriComponents, cellHandle: number): void { - const execution = this._notebookExecutionService.createNotebookCellExecution(URI.revive(uri), cellHandle); - this._executions.set(handle, execution); + $addExecution(rawUri: UriComponents, cellHandle: number): void { + const uri = URI.revive(rawUri); + this._notebookExecutionService.createNotebookCellExecution(uri, cellHandle); + + const cellUri = CellUri.generateCellUri(uri, cellHandle, uri.scheme); + this._executions.add(cellUri.toString()); } $updateExecutions(data: SerializableObjectWithBuffers): void { const updates = data.value; - const groupedUpdates = groupBy(updates, (a, b) => a.executionHandle - b.executionHandle); + const groupedUpdates = groupBy(updates, (a, b) => a.cellHandle - b.cellHandle); groupedUpdates.forEach(datas => { const first = datas[0]; - const execution = this._executions.get(first.executionHandle); - if (!execution) { - return; - } try { - execution.update(datas.map(NotebookDto.fromCellExecuteUpdateDto)); + const uri = URI.revive(first.uri); + this._notebookExecutionService.updateNotebookCellExecution(uri, first.cellHandle, datas.map(NotebookDto.fromCellExecuteUpdateDto)); } catch (e) { onUnexpectedError(e); } }); } - $removeExecution(handle: number): void { - this._executions.delete(handle); + $completeExecution(rawUri: UriComponents, cellHandle: number, data: SerializableObjectWithBuffers): void { + const uri = URI.revive(rawUri); + this._notebookExecutionService.completeNotebookCellExecution(uri, cellHandle, NotebookDto.fromCellExecuteCompleteDto(data.value)); + + const cellUri = CellUri.generateCellUri(uri, cellHandle, uri.scheme); + this._executions.delete(cellUri.toString()); } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f6e82bc208a..4742f3bdf5d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -922,7 +922,7 @@ export interface INotebookKernelDto2 { export interface ICellExecuteOutputEditDto { editType: CellExecutionUpdateType.Output; - executionHandle: number; + uri: UriComponents; cellHandle: number; append?: boolean; outputs: NotebookOutputDto[] @@ -930,21 +930,24 @@ export interface ICellExecuteOutputEditDto { export interface ICellExecuteOutputItemEditDto { editType: CellExecutionUpdateType.OutputItems; - executionHandle: number; + uri: UriComponents; + cellHandle: number; append?: boolean; outputId: string; items: NotebookOutputItemDto[] } export interface ICellExecutionStateUpdateDto extends ICellExecutionStateUpdate { - executionHandle: number; + uri: UriComponents; + cellHandle: number; } export interface ICellExecutionCompleteDto extends ICellExecutionComplete { - executionHandle: number; + uri: UriComponents; + cellHandle: number; } -export type ICellExecuteUpdateDto = ICellExecuteOutputEditDto | ICellExecuteOutputItemEditDto | ICellExecutionStateUpdateDto | ICellExecutionCompleteDto; +export type ICellExecuteUpdateDto = ICellExecuteOutputEditDto | ICellExecuteOutputItemEditDto | ICellExecutionStateUpdateDto; export interface MainThreadNotebookKernelsShape extends IDisposable { $postMessage(handle: number, editorId: string | undefined, message: any): Promise; @@ -953,9 +956,9 @@ export interface MainThreadNotebookKernelsShape extends IDisposable { $removeKernel(handle: number): void; $updateNotebookPriority(handle: number, uri: UriComponents, value: number | undefined): void; - $addExecution(handle: number, uri: UriComponents, cellHandle: number): void; + $addExecution(uri: UriComponents, cellHandle: number): void; $updateExecutions(data: SerializableObjectWithBuffers): void; - $removeExecution(handle: number): void; + $completeExecution(uri: UriComponents, cellHandle: number, data: SerializableObjectWithBuffers): void; } export interface MainThreadNotebookRenderersShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 75895d19dcf..b519a071f6c 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -366,9 +366,6 @@ enum NotebookCellExecutionTaskState { } class NotebookCellExecutionTask extends Disposable { - private static HANDLE = 0; - private _handle = NotebookCellExecutionTask.HANDLE++; - private _onDidChangeState = new Emitter(); readonly onDidChangeState = this._onDidChangeState.event; @@ -391,7 +388,7 @@ class NotebookCellExecutionTask extends Disposable { this._collector = new TimeoutBasedCollector(10, updates => this.update(updates)); this._executionOrder = _cell.internalMetadata.executionOrder; - this._proxy.$addExecution(this._handle, this._cell.notebook.uri, this._cell.handle); + this._proxy.$addExecution(this._cell.notebook.uri, this._cell.handle); } cancel(): void { @@ -448,7 +445,7 @@ class NotebookCellExecutionTask extends Disposable { return this.updateSoon( { editType: CellExecutionUpdateType.Output, - executionHandle: this._handle, + uri: this._document.uri, cellHandle: handle, append, outputs: outputDtos @@ -459,7 +456,8 @@ class NotebookCellExecutionTask extends Disposable { items = NotebookCellOutput.ensureUniqueMimeTypes(asArray(items), true); return this.updateSoon({ editType: CellExecutionUpdateType.OutputItems, - executionHandle: this._handle, + uri: this._document.uri, + cellHandle: this._cell.handle, items: items.map(extHostTypeConverters.NotebookCellOutputItem.from), outputId: output.id, append @@ -476,7 +474,8 @@ class NotebookCellExecutionTask extends Disposable { that._executionOrder = v; that.update([{ editType: CellExecutionUpdateType.ExecutionState, - executionHandle: that._handle, + uri: that._document.uri, + cellHandle: that._cell.handle, executionOrder: that._executionOrder }]); }, @@ -491,7 +490,8 @@ class NotebookCellExecutionTask extends Disposable { that.update({ editType: CellExecutionUpdateType.ExecutionState, - executionHandle: that._handle, + uri: that._document.uri, + cellHandle: that._cell.handle, runStartTime: startTime }); }, @@ -504,18 +504,16 @@ class NotebookCellExecutionTask extends Disposable { that._state = NotebookCellExecutionTaskState.Resolved; that._onDidChangeState.fire(); - that.updateSoon({ - editType: CellExecutionUpdateType.Complete, - executionHandle: that._handle, - runEndTime: endTime, - lastRunSuccess: success - }); - // The last update needs to be ordered correctly and applied immediately, // so we use updateSoon and immediately flush. that._collector.flush(); - that._proxy.$removeExecution(that._handle); + that._proxy.$completeExecution(that._document.uri, that._cell.handle, new SerializableObjectWithBuffers({ + uri: that._document.uri, + cellHandle: that._cell.handle, + runEndTime: endTime, + lastRunSuccess: success + })); }, clearOutput(cell?: vscode.NotebookCell): Thenable { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts deleted file mode 100644 index 37a5b0f439e..00000000000 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts +++ /dev/null @@ -1,71 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CellKind, INotebookTextModel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; -import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; - -export class NotebookEditorKernelManager extends Disposable { - - constructor( - @ICommandService private readonly _commandService: ICommandService, - @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, - @IWorkspaceTrustRequestService private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService, - ) { - super(); - } - - getSelectedOrSuggestedKernel(notebook: INotebookTextModel): INotebookKernel | undefined { - // returns SELECTED or the ONLY available kernel - const info = this._notebookKernelService.getMatchingKernel(notebook); - return info.selected ?? (info.all.length === 1 ? info.all[0] : undefined); - } - - async executeNotebookCells(notebook: INotebookTextModel, cells: Iterable): Promise { - const message = nls.localize('notebookRunTrust', "Executing a notebook cell will run code from this workspace."); - const trust = await this._workspaceTrustRequestService.requestWorkspaceTrust({ message }); - if (!trust) { - return; - } - - let kernel = this.getSelectedOrSuggestedKernel(notebook); - if (!kernel) { - await this._commandService.executeCommand(SELECT_KERNEL_ID); - kernel = this.getSelectedOrSuggestedKernel(notebook); - } - - if (!kernel) { - return; - } - - const cellHandles: number[] = []; - for (const cell of cells) { - if (cell.cellKind !== CellKind.Code || cell.internalMetadata.runState === NotebookCellExecutionState.Pending || cell.internalMetadata.runState === NotebookCellExecutionState.Executing) { - continue; - } - if (!kernel.supportedLanguages.includes(cell.language)) { - continue; - } - cellHandles.push(cell.handle); - } - - if (cellHandles.length > 0) { - this._notebookKernelService.selectKernelForNotebook(kernel, notebook); - await kernel.executeNotebookCellsRequest(notebook.uri, cellHandles); - } - } - - async cancelNotebookCells(notebook: INotebookTextModel, cells: Iterable): Promise { - let kernel = this.getSelectedOrSuggestedKernel(notebook); - if (kernel) { - await kernel.cancelNotebookCellExecution(notebook.uri, Array.from(cells, cell => cell.handle)); - } - } -} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index d8f6ba6ef4b..ff5bf9a1154 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -5,8 +5,8 @@ import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; -import * as aria from 'vs/base/browser/ui/aria/aria'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import * as aria from 'vs/base/browser/ui/aria/aria'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { IAction } from 'vs/base/common/actions'; import { SequencerByKey } from 'vs/base/common/async'; @@ -18,11 +18,13 @@ import { extname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import 'vs/css!./media/notebook'; +import { readFontInfo } from 'vs/editor/browser/config/configuration'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { Range } from 'vs/editor/common/core/range'; import { IEditor } from 'vs/editor/common/editorCommon'; +import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import * as nls from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; @@ -32,45 +34,43 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { registerZIndex, ZIndex } from 'vs/platform/layout/browser/zIndexRegistry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeground, focusBorder, foreground, iconForeground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, toolbarHoverBackground, transparent } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PANEL_BORDER, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors'; import { CellEditState, CellFocusMode, IActiveNotebookEditorDelegate, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorDelegate, INotebookEditorMouseEvent, INotebookEditorOptions, NotebookCellStateChangedEvent, NotebookLayoutChangedEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookDecorationCSSRules, NotebookRefCountedStyleSheet } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorDecorations'; -import { NotebookEditorContextKeys } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys'; -import { NotebookEditorToolbar } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; -import { NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { NotebookCellList, NOTEBOOK_WEBVIEW_BOUNDARY } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; -import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; -import { BackLayerWebView, INotebookWebviewMessage } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; +import { notebookDebug } from 'vs/workbench/contrib/notebook/browser/notebookLogger'; import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys'; import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd'; +import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; +import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; +import { BackLayerWebView, INotebookWebviewMessage } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; import { CodeCellRenderer, MarkupCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; +import { IAckOutputHeight, IMarkupCellInitialization } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, INotebookViewCellsUpdateEvent, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; +import { NotebookDecorationCSSRules, NotebookRefCountedStyleSheet } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorDecorations'; +import { NotebookEditorToolbar } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar'; +import { NotebookEditorContextKeys } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys'; +import { ListTopCellToolbar } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; -import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; -import { IWebview } from 'vs/workbench/contrib/webview/browser/webview'; -import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; -import { readFontInfo } from 'vs/editor/browser/config/configuration'; +import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookOptions, OutputInnerContainerTopPadding } from 'vs/workbench/contrib/notebook/common/notebookOptions'; -import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; +import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; -import { IAckOutputHeight, IMarkupCellInitialization } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; -import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; -import { registerZIndex, ZIndex } from 'vs/platform/layout/browser/zIndexRegistry'; -import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; -import { notebookDebug } from 'vs/workbench/contrib/notebook/browser/notebookLogger'; -import { ListTopCellToolbar } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar'; +import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; +import { IWebview } from 'vs/workbench/contrib/webview/browser/webview'; const $ = DOM.$; @@ -323,7 +323,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD protected readonly _contributions = new Map(); private _scrollBeyondLastLine: boolean; private readonly _insetModifyQueueByOutputId = new SequencerByKey(); - private _kernelManger: NotebookEditorKernelManager; private _cellContextKeyManager: CellContextKeyManager | null = null; private _isVisible = false; private readonly _uuid = generateUuid(); @@ -399,7 +398,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD @IContextMenuService private readonly contextMenuService: IContextMenuService, @IMenuService private readonly menuService: IMenuService, @IThemeService private readonly themeService: IThemeService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @INotebookExecutionService private readonly notebookExecutionService: INotebookExecutionService, ) { super(); this.isEmbedded = creationOptions.isEmbedded ?? false; @@ -418,7 +418,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._register(this.instantiationService.createInstance(NotebookEditorContextKeys, this)); - this._kernelManger = this.instantiationService.createInstance(NotebookEditorKernelManager); this._register(notebookKernelService.onDidChangeSelectedNotebooks(e => { if (isEqual(e.notebook, this.viewModel?.uri)) { this._loadKernelPreloads(); @@ -2052,7 +2051,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } get activeKernel() { - return this.textModel && this._kernelManger.getSelectedOrSuggestedKernel(this.textModel); + return this.textModel && this.notebookExecutionService.getSelectedOrSuggestedKernel(this.textModel); } async cancelNotebookCells(cells?: Iterable): Promise { @@ -2062,7 +2061,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD if (!cells) { cells = this.viewModel.viewCells; } - return this._kernelManger.cancelNotebookCells(this.textModel, cells); + return this.notebookExecutionService.cancelNotebookCellHandles(this.textModel, Array.from(cells).map(cell => cell.handle)); } async executeNotebookCells(cells?: Iterable): Promise { @@ -2072,7 +2071,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD if (!cells) { cells = this.viewModel.viewCells; } - return this._kernelManger.executeNotebookCells(this.textModel, cells); + return this.notebookExecutionService.executeNotebookCells(this.textModel, cells); } //#endregion diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts index 6ae2fba1350..445612437f7 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts @@ -3,28 +3,259 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; -import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import * as nls from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; +import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellEditType, ICellEditOperation, NotebookCellExecutionState, NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { CellExecutionUpdateType, ICellExecuteUpdate, INotebookCellExecution, INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; +import { CellEditType, CellKind, ICellEditOperation, INotebookTextModel, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookTextModelWillAddRemoveEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellExecutionUpdateType, ICellExecuteUpdate, ICellExecutionComplete, INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; +import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -export class NotebookExecutionService implements INotebookExecutionService { +export class NotebookExecutionService extends Disposable implements INotebookExecutionService { declare _serviceBrand: undefined; + private readonly _executions = new ResourceMap(); + constructor( - @INotebookService private readonly _notebookService: INotebookService, + @ICommandService private readonly _commandService: ICommandService, + @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, + @IWorkspaceTrustRequestService private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ILogService private readonly _logService: ILogService, ) { + super(); + + this._register(this._notebookKernelService.onDidChangeSelectedNotebooks(e => { + if (e.newKernel) { + const notebookExecution = this._executions.get(e.notebook); + if (notebookExecution) { + notebookExecution.cancelAll(); + this.checkNotebookExecutionEmpty(e.notebook); + } + } + })); } - createNotebookCellExecution(notebook: URI, cellHandle: number): INotebookCellExecution { - return new CellExecution(notebook, cellHandle, this._notebookService); + createNotebookCellExecution(notebook: URI, cellHandle: number): void { + let notebookExecution = this._executions.get(notebook); + if (!notebookExecution) { + notebookExecution = this._instantiationService.createInstance(NotebookExecution, notebook); + this._executions.set(notebook, notebookExecution); + } + + notebookExecution.addExecution(cellHandle); + } + + getSelectedOrSuggestedKernel(notebook: INotebookTextModel): INotebookKernel | undefined { + // TODO later can be inlined in notebookEditorWidget + // returns SELECTED or the ONLY available kernel + const info = this._notebookKernelService.getMatchingKernel(notebook); + return info.selected ?? (info.all.length === 1 ? info.all[0] : undefined); + } + + async executeNotebookCells(notebook: INotebookTextModel, cells: Iterable): Promise { + const cellsArr = Array.from(cells); + this._logService.debug(`NotebookExecutionService#executeNotebookCells ${JSON.stringify(cellsArr.map(c => c.handle))}`); + const message = nls.localize('notebookRunTrust', "Executing a notebook cell will run code from this workspace."); + const trust = await this._workspaceTrustRequestService.requestWorkspaceTrust({ message }); + if (!trust) { + return; + } + + let kernel = this.getSelectedOrSuggestedKernel(notebook); + if (!kernel) { + await this._commandService.executeCommand(SELECT_KERNEL_ID); + kernel = this.getSelectedOrSuggestedKernel(notebook); + } + + if (!kernel) { + return; + } + + const cellHandles: number[] = []; + for (const cell of cellsArr) { + if (cell.cellKind !== CellKind.Code || cell.internalMetadata.runState === NotebookCellExecutionState.Pending || cell.internalMetadata.runState === NotebookCellExecutionState.Executing) { + continue; + } + if (!kernel.supportedLanguages.includes(cell.language)) { + continue; + } + cellHandles.push(cell.handle); + } + + if (cellHandles.length > 0) { + this._notebookKernelService.selectKernelForNotebook(kernel, notebook); + await kernel.executeNotebookCellsRequest(notebook.uri, cellHandles); + } + } + + async cancelNotebookCellHandles(notebook: INotebookTextModel, cells: Iterable): Promise { + const cellsArr = Array.from(cells); + this._logService.debug(`NotebookExecutionService#cancelNotebookCellHandles ${JSON.stringify(cellsArr)}`); + const kernel = this.getSelectedOrSuggestedKernel(notebook); + if (kernel) { + await kernel.cancelNotebookCellExecution(notebook.uri, cellsArr); + } + } + + async cancelNotebookCells(notebook: INotebookTextModel, cells: Iterable): Promise { + this.cancelNotebookCellHandles(notebook, Array.from(cells, cell => cell.handle)); + } + + updateNotebookCellExecution(notebook: URI, cellHandle: number, updates: ICellExecuteUpdate[]): void { + const notebookExecution = this._executions.get(notebook); + if (!notebookExecution) { + this._logService.error(`notebook execution not found for ${notebook}`); + return; + } + + notebookExecution.updateExecution(cellHandle, updates); + } + + completeNotebookCellExecution(notebook: URI, cellHandle: number, complete: ICellExecutionComplete): void { + const notebookExecution = this._executions.get(notebook); + if (!notebookExecution) { + this._logService.error(`notebook execution not found for ${notebook}`); + return; + } + + notebookExecution.completeExecution(cellHandle, complete); + this.checkNotebookExecutionEmpty(notebook); + } + + private checkNotebookExecutionEmpty(notebook: URI): void { + const notebookExecution = this._executions.get(notebook); + if (!notebookExecution) { + return; + } + + if (notebookExecution.isEmpty()) { + this._logService.debug(`NotebookExecution#dispose ${notebook.toString()}`); + notebookExecution.dispose(); + this._executions.delete(notebook); + } + } + + override dispose(): void { + super.dispose(); + this._executions.forEach(e => e.dispose()); + this._executions.clear(); } } -function updateToEdit(update: ICellExecuteUpdate, cellHandle: number, model: NotebookCellTextModel): ICellEditOperation { +class NotebookExecution extends Disposable { + private readonly _notebookModel: NotebookTextModel; + + private readonly _cellExecutions = new Map(); + + constructor( + notebook: URI, + @INotebookService private readonly _notebookService: INotebookService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @INotebookExecutionService private readonly _notebookExecutionService: INotebookExecutionService, + @ILogService private readonly _logService: ILogService, + ) { + super(); + this._logService.debug(`NotebookExecution#ctor ${notebook.toString()}`); + + const notebookModel = this._notebookService.getNotebookTextModel(notebook); + if (!notebookModel) { + throw new Error('Notebook not found: ' + notebook); + } + + this._notebookModel = notebookModel; + this._register(this._notebookModel.onWillAddRemoveCells(e => this.onWillAddRemoveCells(e))); + this._register(this._notebookModel.onWillDispose(() => this.onWillDisposeDocument())); + } + + private cellLog(cellHandle: number): string { + return `${this._notebookModel.uri.toString()}, ${cellHandle}`; + } + + isEmpty(): boolean { + return this._cellExecutions.size === 0; + } + + cancelAll(): void { + this._logService.debug(`NotebookExecution#cancelAll`); + this._notebookExecutionService.cancelNotebookCellHandles(this._notebookModel, this._cellExecutions.keys()); + } + + addExecution(cellHandle: number) { + this._logService.debug(`NotebookExecution#addExecution ${this.cellLog(cellHandle)}`); + const execution = this._instantiationService.createInstance(CellExecution, cellHandle, this._notebookModel); + this._cellExecutions.set(cellHandle, execution); + } + + updateExecution(cellHandle: number, updates: ICellExecuteUpdate[]): void { + this.logUpdates(cellHandle, updates); + const execution = this._cellExecutions.get(cellHandle); + if (!execution) { + this._logService.error(`no execution for cell ${cellHandle}`); + return; + } + + execution.update(updates); + } + + private logUpdates(cellHandle: number, updates: ICellExecuteUpdate[]): void { + const updateTypes = updates.map(u => CellExecutionUpdateType[u.editType]).join(', '); + this._logService.debug(`NotebookExecution#updateExecution ${this.cellLog(cellHandle)}, [${updateTypes}]`); + } + + completeExecution(cellHandle: number, complete: ICellExecutionComplete): void { + this._logService.debug(`NotebookExecution#completeExecution ${this.cellLog(cellHandle)}`); + + const execution = this._cellExecutions.get(cellHandle); + if (!execution) { + this._logService.error(`no execution for cell ${cellHandle}`); + return; + } + + try { + execution.complete(complete); + } finally { + this._cellExecutions.delete(cellHandle); + } + } + + private onWillDisposeDocument(): void { + this._logService.debug(`NotebookExecution#onWillDisposeDocument`); + this.cancelAll(); + } + + private onWillAddRemoveCells(e: NotebookTextModelWillAddRemoveEvent): void { + const handles = new Set(this._cellExecutions.keys()); + const myDeletedHandles = new Set(); + e.rawEvent.changes.forEach(([start, deleteCount]) => { + if (deleteCount) { + const deletedHandles = this._notebookModel.cells.slice(start, start + deleteCount).map(c => c.handle); + deletedHandles.forEach(h => { + if (handles.has(h)) { + myDeletedHandles.add(h); + } + }); + } + + return false; + }); + + if (myDeletedHandles.size) { + this._logService.debug(`NotebookExecution#onWillAddRemoveCells, ${JSON.stringify([...myDeletedHandles])}`); + this._notebookExecutionService.cancelNotebookCellHandles(this._notebookModel, myDeletedHandles); + } + } +} + +function updateToEdit(update: ICellExecuteUpdate, cellHandle: number): ICellEditOperation { if (update.editType === CellExecutionUpdateType.Output) { return { editType: CellEditType.Output, @@ -39,19 +270,6 @@ function updateToEdit(update: ICellExecuteUpdate, cellHandle: number, model: Not append: update.append, outputId: update.outputId }; - } else if (update.editType === CellExecutionUpdateType.Complete) { - return { - editType: CellEditType.PartialInternalMetadata, - handle: cellHandle, - internalMetadata: { - runState: null, - lastRunSuccess: update.lastRunSuccess, - runStartTime: model.internalMetadata.didPause ? null : model.internalMetadata.runStartTime, - runEndTime: model.internalMetadata.didPause ? null : update.runEndTime, - isPaused: false, - didPause: false - } - }; } else if (update.editType === CellExecutionUpdateType.ExecutionState) { const newInternalMetadata: Partial = { runState: NotebookCellExecutionState.Executing, @@ -72,26 +290,14 @@ function updateToEdit(update: ICellExecuteUpdate, cellHandle: number, model: Not throw new Error('Unknown cell update type'); } -class CellExecution implements INotebookCellExecution, IDisposable { - private readonly _notebookModel: NotebookTextModel; - - private _isDisposed = false; - +class CellExecution { constructor( - readonly notebook: URI, - readonly cellHandle: number, - private readonly _notebookService: INotebookService, + private readonly _cellHandle: number, + private readonly _notebookModel: NotebookTextModel, ) { - const notebookModel = this._notebookService.getNotebookTextModel(notebook); - if (!notebookModel) { - throw new Error('Notebook not found: ' + notebook); - } - - this._notebookModel = notebookModel; - const startExecuteEdit: ICellEditOperation = { editType: CellEditType.PartialInternalMetadata, - handle: cellHandle, + handle: this._cellHandle, internalMetadata: { runState: NotebookCellExecutionState.Pending, executionOrder: null, @@ -102,25 +308,29 @@ class CellExecution implements INotebookCellExecution, IDisposable { } update(updates: ICellExecuteUpdate[]): void { - if (this._isDisposed) { - throw new Error('Cannot update disposed execution'); - } - - const cellModel = this._notebookModel.cells.find(c => c.handle === this.cellHandle); - if (!cellModel) { - throw new Error('Cell not found: ' + this.cellHandle); - } - - const edits = updates.map(update => updateToEdit(update, this.cellHandle, cellModel)); + const edits = updates.map(update => updateToEdit(update, this._cellHandle)); this._applyExecutionEdits(edits); - - if (updates.some(u => u.editType === CellExecutionUpdateType.Complete)) { - this.dispose(); - } } - dispose(): void { - this._isDisposed = true; + complete(completionData: ICellExecutionComplete): void { + const cellModel = this._notebookModel.cells.find(c => c.handle === this._cellHandle); + if (!cellModel) { + throw new Error('Cell not found: ' + this._cellHandle); + } + + const edit: ICellEditOperation = { + editType: CellEditType.PartialInternalMetadata, + handle: this._cellHandle, + internalMetadata: { + runState: null, + lastRunSuccess: completionData.lastRunSuccess, + runStartTime: cellModel.internalMetadata.didPause ? null : cellModel.internalMetadata.runStartTime, + runEndTime: cellModel.internalMetadata.didPause ? null : completionData.runEndTime, + isPaused: false, + didPause: false + } + }; + this._applyExecutionEdits([edit]); } private _applyExecutionEdits(edits: ICellEditOperation[]): void { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index c6ebc80fc36..27ec81cefc9 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -159,6 +159,7 @@ export class NotebookEventEmitter extends PauseableEmitter = this._register(new Emitter()); private readonly _onWillAddRemoveCells = this._register(new Emitter()); private readonly _onDidChangeContent = this._register(new Emitter()); @@ -344,6 +345,12 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } override dispose() { + if (this._isDisposed) { + // NotebookEditorModel can be disposed twice, don't fire onWillDispose again + return; + } + + this._isDisposed = true; this._onWillDispose.fire(); this._undoService.removeElements(this.uri); diff --git a/src/vs/workbench/contrib/notebook/common/notebookExecutionService.ts b/src/vs/workbench/contrib/notebook/common/notebookExecutionService.ts index 0f14b023a82..72568f60fa5 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookExecutionService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookExecutionService.ts @@ -5,13 +5,14 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IOutputDto, IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookTextModel, IOutputDto, IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export enum CellExecutionUpdateType { Output = 1, OutputItems = 2, ExecutionState = 3, - Complete = 4, } export interface ICellExecuteOutputEdit { @@ -28,7 +29,7 @@ export interface ICellExecuteOutputItemEdit { items: IOutputItemDto[] } -export type ICellExecuteUpdate = ICellExecuteOutputEdit | ICellExecuteOutputItemEdit | ICellExecutionStateUpdate | ICellExecutionComplete; +export type ICellExecuteUpdate = ICellExecuteOutputEdit | ICellExecuteOutputItemEdit | ICellExecutionStateUpdate; export interface ICellExecutionStateUpdate { editType: CellExecutionUpdateType.ExecutionState; @@ -37,21 +38,20 @@ export interface ICellExecutionStateUpdate { } export interface ICellExecutionComplete { - editType: CellExecutionUpdateType.Complete; runEndTime?: number; lastRunSuccess?: boolean; } -export interface INotebookCellExecution { - readonly notebook: URI; - readonly cellHandle: number; - update(updates: ICellExecuteUpdate[]): void; -} - export const INotebookExecutionService = createDecorator('INotebookExecutionService'); export interface INotebookExecutionService { _serviceBrand: undefined; - createNotebookCellExecution(notebook: URI, cellHandle: number): INotebookCellExecution; + createNotebookCellExecution(notebook: URI, cellHandle: number): void; + updateNotebookCellExecution(notebook: URI, cellHandle: number, updates: ICellExecuteUpdate[]): void; + completeNotebookCellExecution(notebook: URI, cellHandle: number, complete: ICellExecutionComplete): void; + getSelectedOrSuggestedKernel(notebook: INotebookTextModel): INotebookKernel | undefined; + executeNotebookCells(notebook: INotebookTextModel, cells: Iterable): Promise; + cancelNotebookCells(notebook: INotebookTextModel, cells: Iterable): Promise; + cancelNotebookCellHandles(notebook: INotebookTextModel, cells: Iterable): Promise; }