diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 0fc67bc05e4..caf759d1c0d 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -19,7 +19,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSION_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { NOTEBOOK_ACTIONS_CATEGORY, SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; -import { getNotebookEditorFromEditorPane, INotebookEditor, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { getNotebookEditorFromEditorPane, INotebookEditor, KERNEL_EXTENSIONS, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; @@ -44,7 +44,7 @@ registerAction2(class extends Action2 { id: MenuId.EditorTitle, when: ContextKeyExpr.and( NOTEBOOK_IS_ACTIVE_EDITOR, - ContextKeyExpr.or(NOTEBOOK_KERNEL_COUNT.notEqualsTo(0), NOTEBOOK_VIEW_TYPE.isEqualTo('jupyter-notebook')), + ContextKeyExpr.or(NOTEBOOK_KERNEL_COUNT.notEqualsTo(0), NOTEBOOK_MISSING_KERNEL_EXTENSION), ContextKeyExpr.notEquals('config.notebook.globalToolbar', true) ), group: 'navigation', @@ -52,7 +52,7 @@ registerAction2(class extends Action2 { }, { id: MenuId.NotebookToolbar, when: ContextKeyExpr.and( - ContextKeyExpr.or(NOTEBOOK_KERNEL_COUNT.notEqualsTo(0), NOTEBOOK_VIEW_TYPE.isEqualTo('jupyter-notebook')), + ContextKeyExpr.or(NOTEBOOK_KERNEL_COUNT.notEqualsTo(0), NOTEBOOK_MISSING_KERNEL_EXTENSION), ContextKeyExpr.equals('config.notebook.globalToolbar', true) ), group: 'status', @@ -152,7 +152,7 @@ registerAction2(class extends Action2 { } { return res; } }); - if (!picks.length) { + if (!all.length && KERNEL_EXTENSIONS.get(notebook.viewType)) { picks.push({ id: 'install', label: nls.localize('installKernels', "Install kernels from the marketplace"), @@ -172,7 +172,7 @@ registerAction2(class extends Action2 { if (pick) { if (pick.id === 'install') { - await this._showJupyterExtension(viewletService); + await this._showKernelExtension(viewletService, notebook.viewType); } else if ('kernel' in pick) { newKernel = pick.kernel; } @@ -186,10 +186,13 @@ registerAction2(class extends Action2 { return false; } - private async _showJupyterExtension(viewletService: IViewletService) { - const viewlet = await viewletService.openViewlet(EXTENSION_VIEWLET_ID, true); - const view = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer | undefined; - view?.search('@id:ms-toolsai.jupyter'); + private async _showKernelExtension(viewletService: IViewletService, viewType: string) { + const extId = KERNEL_EXTENSIONS.get(viewType); + if (extId) { + const viewlet = await viewletService.openViewlet(EXTENSION_VIEWLET_ID, true); + const view = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer | undefined; + view?.search(`@id:${extId}`); + } } }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 308d2f27948..d352cbed07a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -73,7 +73,7 @@ export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey('notebo export const NOTEBOOK_KERNEL_COUNT = new RawContextKey('notebookKernelCount', 0); export const NOTEBOOK_KERNEL_SELECTED = new RawContextKey('notebookKernelSelected', false); export const NOTEBOOK_INTERRUPTIBLE_KERNEL = new RawContextKey('notebookInterruptibleKernel', false); - +export const NOTEBOOK_MISSING_KERNEL_EXTENSION = new RawContextKey('notebookMissingKernelExtension', false); export const NOTEBOOK_HAS_OUTPUTS = new RawContextKey('notebookHasOutputs', false); //#endregion @@ -86,6 +86,17 @@ export const QUIT_EDIT_CELL_COMMAND_ID = 'notebook.cell.quitEdit'; //#endregion +//#region Notebook extensions + +// Hardcoding viewType/extension ID for now. TODO these should be replaced once we can +// look them up in the marketplace dynamically. +export const JUPYTER_EXTENSION_ID = 'ms-toolsai.jupyter'; +export const KERNEL_EXTENSIONS = new Map([ + ['jupyter-notebook', JUPYTER_EXTENSION_ID], +]); + +//#endregion + //#region Output related types export const enum RenderOutputType { Mainframe, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys.ts index ad78f3e0dfd..b53eafbb70b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys.ts @@ -5,10 +5,11 @@ import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ICellViewModel, INotebookEditor, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICellViewModel, INotebookEditor, KERNEL_EXTENSIONS, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; export class NotebookEditorContextKeys { @@ -18,7 +19,8 @@ export class NotebookEditorContextKeys { private readonly _someCellRunning: IContextKey; private readonly _hasOutputs: IContextKey; private readonly _useConsolidatedOutputButton: IContextKey; - private _viewType!: IContextKey; + private readonly _viewType!: IContextKey; + private readonly _missingKernelExtension: IContextKey; private readonly _disposables = new DisposableStore(); private readonly _viewModelDisposables = new DisposableStore(); @@ -29,6 +31,7 @@ export class NotebookEditorContextKeys { private readonly _editor: INotebookEditor, @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, @IContextKeyService contextKeyService: IContextKeyService, + @IExtensionService private readonly _extensionService: IExtensionService ) { this._notebookKernelCount = NOTEBOOK_KERNEL_COUNT.bindTo(contextKeyService); this._notebookKernelSelected = NOTEBOOK_KERNEL_SELECTED.bindTo(contextKeyService); @@ -37,6 +40,7 @@ export class NotebookEditorContextKeys { this._useConsolidatedOutputButton = NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON.bindTo(contextKeyService); this._hasOutputs = NOTEBOOK_HAS_OUTPUTS.bindTo(contextKeyService); this._viewType = NOTEBOOK_VIEW_TYPE.bindTo(contextKeyService); + this._missingKernelExtension = NOTEBOOK_MISSING_KERNEL_EXTENSION.bindTo(contextKeyService); this._handleDidChangeModel(); this._updateForNotebookOptions(); @@ -44,9 +48,8 @@ export class NotebookEditorContextKeys { this._disposables.add(_editor.onDidChangeModel(this._handleDidChangeModel, this)); this._disposables.add(_notebookKernelService.onDidAddKernel(this._updateKernelContext, this)); this._disposables.add(_notebookKernelService.onDidChangeSelectedNotebooks(this._updateKernelContext, this)); - this._disposables.add(_editor.notebookOptions.onDidChangeOptions(() => { - this._updateForNotebookOptions(); - })); + this._disposables.add(_editor.notebookOptions.onDidChangeOptions(this._updateForNotebookOptions, this)); + this._disposables.add(_extensionService.onDidChangeExtensions(this._updateForInstalledExtension, this)); } dispose(): void { @@ -118,6 +121,7 @@ export class NotebookEditorContextKeys { } recomputeOutputsExistence(); + this._updateForInstalledExtension(); this._viewModelDisposables.add(this._editor.viewModel.onDidChangeViewCells(e => { e.splices.reverse().forEach(splice => { @@ -131,6 +135,17 @@ export class NotebookEditorContextKeys { this._viewType.set(this._editor.viewModel.viewType); } + private async _updateForInstalledExtension(): Promise { + if (!this._editor.hasModel()) { + return; + } + + const viewType = this._editor.viewModel.viewType; + const kernelExtensionId = KERNEL_EXTENSIONS.get(viewType); + this._missingKernelExtension.set( + !!kernelExtensionId && !(await this._extensionService.getExtension(kernelExtensionId))); + } + private _updateKernelContext(): void { if (!this._editor.hasModel()) { this._notebookKernelCount.reset(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts index c483e707394..dc811060bc9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts @@ -23,7 +23,7 @@ import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/commo import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSION_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; -import { CodeCellRenderTemplate, ICellOutputViewModel, ICellViewModel, IInsetRenderOutput, INotebookEditor, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CodeCellRenderTemplate, ICellOutputViewModel, ICellViewModel, IInsetRenderOutput, INotebookEditor, IRenderOutput, JUPYTER_EXTENSION_ID, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { mimetypeIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; @@ -440,7 +440,7 @@ export class CellOutputElement extends Disposable { private async _showJupyterExtension() { const viewlet = await this.viewletService.openViewlet(EXTENSION_VIEWLET_ID, true); const view = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer | undefined; - view?.search('@id:ms-toolsai.jupyter'); + view?.search(`@id:${JUPYTER_EXTENSION_ID}`); } private _generateRendererInfo(renderId: string | undefined): string {