diff --git a/news/1 Enhancements/9717.md b/news/1 Enhancements/9717.md new file mode 100644 index 00000000000..fc3f12b0550 --- /dev/null +++ b/news/1 Enhancements/9717.md @@ -0,0 +1 @@ +Enabled the Interactive Window in web. diff --git a/package.json b/package.json index 26ac0f0dcac..2f188d8e1a2 100644 --- a/package.json +++ b/package.json @@ -376,7 +376,7 @@ "command": "jupyter.runcurrentcell", "title": "%jupyter.command.jupyter.runcurrentcell.title%", "category": "Jupyter", - "enablement": "isWorkspaceTrusted && !jupyter.webExtension" + "enablement": "isWorkspaceTrusted" }, { "command": "jupyter.debugcell", @@ -490,19 +490,19 @@ "command": "jupyter.runcurrentcelladvance", "title": "%jupyter.command.jupyter.runcurrentcelladvance.title%", "category": "Jupyter", - "enablement": "isWorkspaceTrusted && !jupyter.webExtension" + "enablement": "isWorkspaceTrusted" }, { "command": "jupyter.runcurrentcellandallbelow.palette", "title": "%jupyter.command.jupyter.runcurrentcellandallbelow.palette.title%", "category": "Jupyter", - "enablement": "isWorkspaceTrusted && !jupyter.webExtension" + "enablement": "isWorkspaceTrusted" }, { "command": "jupyter.runallcellsabove.palette", "title": "%jupyter.command.jupyter.runallcellsabove.palette.title%", "category": "Jupyter", - "enablement": "isWorkspaceTrusted && !jupyter.webExtension" + "enablement": "isWorkspaceTrusted" }, { "command": "jupyter.debugcurrentcell.palette", @@ -520,13 +520,13 @@ "command": "jupyter.createnewinteractive", "title": "%jupyter.command.jupyter.createnewinteractive.title%", "category": "Jupyter", - "enablement": "isWorkspaceTrusted && !jupyter.webExtension" + "enablement": "isWorkspaceTrusted" }, { "command": "jupyter.runFileInteractive", "title": "%jupyter.command.jupyter.runFileInteractive.title%", "category": "Jupyter", - "enablement": "isWorkspaceTrusted && !jupyter.webExtension" + "enablement": "isWorkspaceTrusted" }, { "command": "jupyter.debugFileInteractive", @@ -538,25 +538,25 @@ "command": "jupyter.runallcells", "title": "%jupyter.command.jupyter.runallcells.title%", "category": "Jupyter", - "enablement": "isWorkspaceTrusted && !jupyter.webExtension" + "enablement": "isWorkspaceTrusted" }, { "command": "jupyter.runallcellsabove", "title": "%jupyter.command.jupyter.runallcellsabove.title%", "category": "Jupyter", - "enablement": "isWorkspaceTrusted && !jupyter.webExtension" + "enablement": "isWorkspaceTrusted" }, { "command": "jupyter.runcellandallbelow", "title": "%jupyter.command.jupyter.runcellandallbelow.title%", "category": "Jupyter", - "enablement": "isWorkspaceTrusted && !jupyter.webExtension" + "enablement": "isWorkspaceTrusted" }, { "command": "jupyter.runcell", "title": "%jupyter.command.jupyter.runcell.title%", "category": "Jupyter", - "enablement": "isWorkspaceTrusted && !jupyter.webExtension" + "enablement": "isWorkspaceTrusted" }, { "command": "jupyter.runtoline", @@ -806,7 +806,6 @@ "command": "jupyter.interactive.clearAllCells", "title": "%DataScience.interactiveClearAllCells%", "icon": "$(close)", - "enablement": "!jupyter.webExtension", "category": "Jupyter" }, { @@ -828,7 +827,7 @@ "title": "%DataScience.exportDialogTitle%", "shortTitle": "%DataScience.exportAsNotebook.shorttitle%", "icon": "$(save-as)", - "enablement": "notebookType == interactive", + "enablement": "notebookType == interactive && !jupyter.webExtension", "category": "Jupyter" }, { @@ -838,7 +837,7 @@ "light": "resources/light/export_to_python.svg", "dark": "resources/dark/export_to_python.svg" }, - "enablement": "notebookType == interactive", + "enablement": "notebookType == interactive && !jupyter.webExtension", "category": "Jupyter" }, { @@ -1807,7 +1806,7 @@ }, "jupyter.codeLenses": { "type": "string", - "default": "jupyter.runcell, jupyter.runallcellsabove, jupyter.debugcell", + "default": "jupyter.runcell, jupyter.runallcellsabove, jupyter.debugcell", "description": "Set of commands to put as code lens above a cell.", "scope": "resource" }, diff --git a/package.nls.json b/package.nls.json index a817251fd5c..6cf0d6d8217 100644 --- a/package.nls.json +++ b/package.nls.json @@ -534,6 +534,7 @@ "DataScience.interactiveWindowModeBannerSwitchYes": "Yes", "DataScience.interactiveWindowModeBannerSwitchAlways": "Always", "DataScience.interactiveWindowModeBannerSwitchNo": "No", + "DataScience.noKernelsSpecifyRemote": "No kernel available for cell execution, please [specify a Jupyter server for connections](command:jupyter.selectjupyteruri)", "DataScience.ipykernelNotInstalled": "IPyKernel not installed into interpreter {0}", "DataScience.ipykernelNotInstalledBecauseCanceled": "IPyKernel not installed into interpreter. Installation canceled.", "DataScience.needIpykernel6": "Ipykernel setup required for this feature", diff --git a/src/interactive-window/editor-integration/codeLensFactory.ts b/src/interactive-window/editor-integration/codeLensFactory.ts index d676833e795..d7dda899646 100644 --- a/src/interactive-window/editor-integration/codeLensFactory.ts +++ b/src/interactive-window/editor-integration/codeLensFactory.ts @@ -17,7 +17,13 @@ import { import { IDocumentManager, IVSCodeNotebook, IWorkspaceService } from '../../platform/common/application/types'; import { traceWarning, traceInfoIfCI } from '../../platform/logging'; -import { ICellRange, IConfigurationService, IDisposableRegistry, Resource } from '../../platform/common/types'; +import { + ICellRange, + IConfigurationService, + IDisposableRegistry, + IsWebExtension, + Resource +} from '../../platform/common/types'; import * as localize from '../../platform/common/utils/localize'; import { getInteractiveCellMetadata } from '../helpers'; import { IKernelProvider } from '../../kernels/types'; @@ -57,7 +63,8 @@ export class CodeLensFactory implements ICodeLensFactory { @inject(IDisposableRegistry) disposables: IDisposableRegistry, @inject(IGeneratedCodeStorageFactory) private readonly generatedCodeStorageFactory: IGeneratedCodeStorageFactory, - @inject(IKernelProvider) kernelProvider: IKernelProvider + @inject(IKernelProvider) kernelProvider: IKernelProvider, + @inject(IsWebExtension) private readonly isWebExtension: boolean ) { this.documentManager.onDidCloseTextDocument(this.onClosedDocument, this, disposables); this.workspace.onDidGrantWorkspaceTrust(() => this.codeLensCache.clear(), this, disposables); @@ -234,9 +241,10 @@ export class CodeLensFactory implements ICodeLensFactory { fullCommandList = fullCommandList.concat(CodeLensCommands.DefaultDebuggingLenses); } + let commandsToBeDisabled: string[] = []; // If workspace is not trusted, then exclude execution related commands. if (!this.workspace.isTrusted) { - const commandsToBeDisabledIfNotTrusted = [ + commandsToBeDisabled = [ ...CodeLensCommands.DebuggerCommands, ...CodeLensCommands.DebuggerCommands, Commands.RunAllCells, @@ -256,8 +264,14 @@ export class CodeLensFactory implements ICodeLensFactory { Commands.DebugStop, Commands.RunCellAndAllBelowPalette ]; - fullCommandList = fullCommandList.filter((item) => !commandsToBeDisabledIfNotTrusted.includes(item)); } + if (this.isWebExtension) { + commandsToBeDisabled.push(Commands.DebugCell); + } + if (commandsToBeDisabled) { + fullCommandList = fullCommandList.filter((item) => !commandsToBeDisabled.includes(item)); + } + return fullCommandList; } diff --git a/src/interactive-window/interactiveWindow.ts b/src/interactive-window/interactiveWindow.ts index ec29f9d149b..77b389daf4d 100644 --- a/src/interactive-window/interactiveWindow.ts +++ b/src/interactive-window/interactiveWindow.ts @@ -126,7 +126,7 @@ export class InteractiveWindow implements IInteractiveWindowLoadable { private readonly exportDialog: IExportDialog, private readonly notebookControllerManager: INotebookControllerManager, private readonly serviceContainer: IServiceContainer, - private readonly interactiveWindowDebugger: IInteractiveWindowDebugger, + private readonly interactiveWindowDebugger: IInteractiveWindowDebugger | undefined, private readonly errorHandler: IDataScienceErrorHandler, preferredController: IVSCodeNotebookController | undefined, public readonly notebookEditor: NotebookEditor, @@ -134,7 +134,8 @@ export class InteractiveWindow implements IInteractiveWindowLoadable { public readonly appShell: IApplicationShell, private readonly codeGeneratorFactory: ICodeGeneratorFactory, private readonly storageFactory: IGeneratedCodeStorageFactory, - private readonly debuggingManager: IInteractiveWindowDebuggingManager + private readonly debuggingManager: IInteractiveWindowDebuggingManager, + private readonly isWebExtension: boolean ) { // Set our owner and first submitter if (this._owner) { @@ -160,6 +161,8 @@ export class InteractiveWindow implements IInteractiveWindowLoadable { if (preferredController) { // Also start connecting to our kernel but don't wait for it to finish this.startKernel(preferredController.controller, preferredController.connection).ignoreErrors(); + } else if (this.isWebExtension) { + this.insertInfoMessage(DataScience.noKernelsSpecifyRemote()).ignoreErrors(); } } @@ -277,9 +280,13 @@ export class InteractiveWindow implements IInteractiveWindowLoadable { kernelMetadata: KernelConnectionMetadata, reason: SysInfoReason ): Promise { + const message = this.getSysInfoMessage(kernelMetadata, reason); + return this.insertInfoMessage(message); + } + + private async insertInfoMessage(message: string): Promise { if (!this._insertSysInfoPromise) { const func = async () => { - const message = this.getSysInfoMessage(kernelMetadata, reason); await chainWithPendingUpdates(this.notebookDocument, (edit) => { const markdownCell = new NotebookCellData(NotebookCellKind.Markup, message, MARKDOWN_LANGUAGE); markdownCell.metadata = { isInteractiveWindowMessageCell: true, isPlaceholder: true }; @@ -296,6 +303,7 @@ export class InteractiveWindow implements IInteractiveWindowLoadable { } return this._insertSysInfoPromise; } + private updateSysInfoMessage(newMessage: string, finish: boolean, cellPromise: Promise) { if (finish) { this._insertSysInfoPromise = undefined; @@ -488,7 +496,7 @@ export class InteractiveWindow implements IInteractiveWindowLoadable { private async submitCodeImpl(code: string, fileUri: Uri, line: number, isDebug: boolean) { // Do not execute or render empty cells - if (this.cellMatcher.isEmptyCell(code)) { + if (this.cellMatcher.isEmptyCell(code) || !this.currentKernelInfo.controller) { return true; } @@ -571,10 +579,14 @@ export class InteractiveWindow implements IInteractiveWindowLoadable { if (isDebug && (settings.forceIPyKernelDebugger || !isLocalConnection(kernel.kernelConnectionMetadata))) { // New ipykernel 7 debugger using the Jupyter protocol. await this.debuggingManager.start(this.notebookEditor, cell); - } else if (isDebug && isLocalConnection(kernel.kernelConnectionMetadata)) { + } else if ( + isDebug && + isLocalConnection(kernel.kernelConnectionMetadata) && + this.interactiveWindowDebugger + ) { // Old ipykernel 6 debugger. // If debugging attach to the kernel but don't enable tracing just yet - detachKernel = async () => this.interactiveWindowDebugger.detach(kernel); + detachKernel = async () => this.interactiveWindowDebugger?.detach(kernel); await this.interactiveWindowDebugger.attach(kernel); await this.interactiveWindowDebugger.updateSourceMaps( this.storageFactory.get({ notebook: cell.notebook })?.all || [] diff --git a/src/interactive-window/interactiveWindowProvider.ts b/src/interactive-window/interactiveWindowProvider.ts index 23ac7cac8bb..0d110573944 100644 --- a/src/interactive-window/interactiveWindowProvider.ts +++ b/src/interactive-window/interactiveWindowProvider.ts @@ -22,6 +22,7 @@ import { IDisposableRegistry, IMemento, InteractiveWindowMode, + IsWebExtension, Resource } from '../platform/common/types'; import { chainable } from '../platform/common/utils/decorators'; @@ -166,7 +167,7 @@ export class InteractiveWindowProvider implements IInteractiveWindowProvider, IA this.serviceContainer.get(IExportDialog), this.notebookControllerManager, this.serviceContainer, - this.serviceContainer.get(IInteractiveWindowDebugger), + this.serviceContainer.tryGet(IInteractiveWindowDebugger), this.serviceContainer.get(IDataScienceErrorHandler), preferredController, editor, @@ -174,7 +175,8 @@ export class InteractiveWindowProvider implements IInteractiveWindowProvider, IA this.appShell, this.serviceContainer.get(ICodeGeneratorFactory), this.serviceContainer.get(IGeneratedCodeStorageFactory), - this.serviceContainer.get(IInteractiveWindowDebuggingManager) + this.serviceContainer.get(IInteractiveWindowDebuggingManager), + this.serviceContainer.get(IsWebExtension) ); this._windows.push(result); diff --git a/src/notebooks/controllers/notebookControllerManager.ts b/src/notebooks/controllers/notebookControllerManager.ts index 3d99fbfa51e..6b5bb814e17 100644 --- a/src/notebooks/controllers/notebookControllerManager.ts +++ b/src/notebooks/controllers/notebookControllerManager.ts @@ -233,7 +233,8 @@ export class NotebookControllerManager implements INotebookControllerManager, IE ? this.notebook.notebookDocuments.find((item) => item.notebookType === notebookType) : undefined; const controller = await this.createDefaultRemoteController(notebookType, notebook); - if (controller) { + // If we're running on web, there is no active interpreter to fall back to + if (controller || this.isWeb) { return controller; } traceVerbose('No default remote controller, hence returning the active interpreter'); diff --git a/src/platform/common/utils/localize.ts b/src/platform/common/utils/localize.ts index 86417f86482..756673d9c96 100644 --- a/src/platform/common/utils/localize.ts +++ b/src/platform/common/utils/localize.ts @@ -1025,6 +1025,10 @@ export namespace DataScience { ); export const connected = localize('DataScience.connected', 'Connected'); export const disconnected = localize('DataScience.disconnected', 'Disconnected'); + export const noKernelsSpecifyRemote = localize( + 'DataScience.noKernelsSpecifyRemote', + 'No kernel available for cell execution, please [specify a Jupyter server for connections](command:jupyter.selectjupyteruri)' + ); export const ipykernelNotInstalled = localize( 'DataScience.ipykernelNotInstalled', 'IPyKernel not installed into interpreter {0}' diff --git a/src/test/datascience/.vscode/settings.json b/src/test/datascience/.vscode/settings.json index 0b25ad38429..7f7464517b0 100644 --- a/src/test/datascience/.vscode/settings.json +++ b/src/test/datascience/.vscode/settings.json @@ -30,5 +30,6 @@ // Python experiments have to be on for insiders to work "python.experiments.enabled": true, "jupyter.generateSVGPlots": false, - "jupyter.forceIPyKernelDebugger": false + "jupyter.forceIPyKernelDebugger": true, + "python.defaultInterpreterPath": "python" } diff --git a/src/test/datascience/editor-integration/codewatcher.unit.test.ts b/src/test/datascience/editor-integration/codewatcher.unit.test.ts index b6c0e06560a..ab659eff5a9 100644 --- a/src/test/datascience/editor-integration/codewatcher.unit.test.ts +++ b/src/test/datascience/editor-integration/codewatcher.unit.test.ts @@ -163,7 +163,8 @@ suite('DataScience Code Watcher Unit Tests', () => { instance(notebook), disposables, instance(storageFactory), - instance(kernelProvider) + instance(kernelProvider), + false ); serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(ICodeWatcher)))