diff --git a/TELEMETRY.md b/TELEMETRY.md index 572b4f21c12..efce2f4affe 100644 --- a/TELEMETRY.md +++ b/TELEMETRY.md @@ -949,7 +949,7 @@ No properties for event ``` -[src/kernels/kernel.base.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.base.ts) +[src/kernels/kernel.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.ts) ```typescript } public async executeCell(cell: NotebookCell, codeOverride?: string): Promise { @@ -1908,7 +1908,7 @@ No description provided ## Locations Used -[src/kernels/kernel.base.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.base.ts) +[src/kernels/kernel.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.ts) ```typescript await this.executeSilently(session, startupCode, { traceErrors: true, @@ -2848,7 +2848,7 @@ No properties for event ``` -[src/kernels/kernel.base.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.base.ts) +[src/kernels/kernel.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.ts) ```typescript await (this._jupyterSessionPromise ? this.kernelExecution.restart(this._jupyterSessionPromise) @@ -2860,7 +2860,7 @@ No properties for event ``` -[src/kernels/kernel.base.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.base.ts) +[src/kernels/kernel.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.ts) ```typescript this.restarting = undefined; // If we get a kernel promise failure, then restarting timed out. Just shutdown and restart the entire server. @@ -4729,7 +4729,7 @@ No description provided ## Locations Used -[src/kernels/kernel.base.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.base.ts) +[src/kernels/kernel.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.ts) ```typescript await this.executeSilently(session, this.getUserStartupCommands(), { traceErrors: true, @@ -7311,7 +7311,7 @@ No properties for event ## Locations Used -[src/kernels/kernel.base.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.base.ts) +[src/kernels/kernel.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.ts) ```typescript // Setup telemetry if (!this.perceivedJupyterStartupTelemetryCaptured) { @@ -7323,7 +7323,7 @@ No properties for event ``` -[src/kernels/kernel.base.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.base.ts) +[src/kernels/kernel.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.ts) ```typescript sendKernelTelemetryEvent( @@ -8757,7 +8757,7 @@ No properties for event ## Locations Used -[src/kernels/kernel.base.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.base.ts) +[src/kernels/kernel.ts](https://github.com/microsoft/vscode-jupyter/tree/main/src/kernels/kernel.ts) ```typescript sendTelemetryEvent(Telemetry.PerceivedJupyterStartupNotebook, stopWatch.elapsedTime); executionPromise diff --git a/src/interactive-window/debugger/interactiveWindowDebugger.node.ts b/src/interactive-window/debugger/interactiveWindowDebugger.node.ts index d8eb6f0301f..1590902a47b 100644 --- a/src/interactive-window/debugger/interactiveWindowDebugger.node.ts +++ b/src/interactive-window/debugger/interactiveWindowDebugger.node.ts @@ -12,7 +12,7 @@ import { DataScience } from '../../platform/common/utils/localize'; import { Identifiers } from '../../platform/common/constants'; import { Telemetry } from '../../telemetry'; import { JupyterDebuggerNotInstalledError } from '../../kernels/errors/jupyterDebuggerNotInstalledError'; -import { getPlainTextOrStreamOutput } from '../../kernels/kernel.base'; +import { getPlainTextOrStreamOutput } from '../../kernels/kernel'; import { IKernel, isLocalConnection } from '../../kernels/types'; import { IInteractiveWindowDebugger } from '../types'; import { IFileGeneratedCodes } from '../editor-integration/types'; diff --git a/src/interactive-window/debugger/startupCodeProvider.ts b/src/interactive-window/debugger/startupCodeProvider.ts new file mode 100644 index 00000000000..ecf2951bfa3 --- /dev/null +++ b/src/interactive-window/debugger/startupCodeProvider.ts @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; +import { IKernel, isLocalConnection, IStartupCodeProvider, StartupCodePriority } from '../../kernels/types'; +import { InteractiveWindowView } from '../../platform/common/constants'; +import { IFileSystem } from '../../platform/common/platform/types'; +import { IConfigurationService, IExtensionContext, IsWebExtension } from '../../platform/common/types'; + +@injectable() +export class InteractiveWindowDebuggingStartupCodeProvider implements IStartupCodeProvider { + public priority = StartupCodePriority.Debugging; + private addRunCellHookContents?: Promise; + + constructor( + @inject(IConfigurationService) private readonly configService: IConfigurationService, + @inject(IFileSystem) private readonly fs: IFileSystem, + @inject(IExtensionContext) private readonly context: IExtensionContext, + @inject(IsWebExtension) private readonly isWebExtension: boolean + ) {} + + async getCode(kernel: IKernel): Promise { + if (!this.isWebExtension) { + const useNewDebugger = this.configService.getSettings(undefined).forceIPyKernelDebugger === true; + if (useNewDebugger) { + return []; + } + if (!isLocalConnection(kernel.kernelConnectionMetadata)) { + return []; + } + } + + if (kernel.notebook?.notebookType === InteractiveWindowView) { + // If using ipykernel 6, we need to set the IPYKERNEL_CELL_NAME so that + // debugging can work. However this code is harmless for IPYKERNEL 5 so just always do it + if (!this.addRunCellHookContents) { + this.addRunCellHookContents = this.fs.readFile( + Uri.joinPath( + this.context.extensionUri, + 'pythonFiles', + 'vscode_datascience_helpers', + 'kernel', + 'addRunCellHook.py' + ) + ); + } + const addRunCellHook = await this.addRunCellHookContents; + + return addRunCellHook.splitLines({ trim: false }); + } + return []; + } +} diff --git a/src/interactive-window/serviceRegistry.node.ts b/src/interactive-window/serviceRegistry.node.ts index 74ad1b16a29..f6acf7902fc 100644 --- a/src/interactive-window/serviceRegistry.node.ts +++ b/src/interactive-window/serviceRegistry.node.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. 'use strict'; -import { ITracebackFormatter } from '../kernels/types'; +import { IStartupCodeProvider, ITracebackFormatter } from '../kernels/types'; import { IExtensionSyncActivationService, IExtensionSingleActivationService } from '../platform/activation/types'; import { IJupyterExtensionBanner } from '../platform/common/types'; import { IServiceManager } from '../platform/ioc/types'; @@ -28,6 +28,7 @@ import { IInteractiveWindowDebugger, IInteractiveWindowDebuggingManager, IIntera import { InteractiveWindowDebugger } from './debugger/interactiveWindowDebugger.node'; import { InteractiveWindowDebuggingManager } from './debugger/jupyter/debuggingManager'; import { BANNER_NAME_INTERACTIVE_SHIFTENTER, InteractiveShiftEnterBanner } from './shiftEnterBanner'; +import { InteractiveWindowDebuggingStartupCodeProvider } from './debugger/startupCodeProvider'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IInteractiveWindowProvider, InteractiveWindowProvider); @@ -64,4 +65,8 @@ export function registerTypes(serviceManager: IServiceManager) { InteractiveShiftEnterBanner, BANNER_NAME_INTERACTIVE_SHIFTENTER ); + serviceManager.addSingleton( + IStartupCodeProvider, + InteractiveWindowDebuggingStartupCodeProvider + ); } diff --git a/src/interactive-window/serviceRegistry.web.ts b/src/interactive-window/serviceRegistry.web.ts index 3c85221e480..5f0b60ee301 100644 --- a/src/interactive-window/serviceRegistry.web.ts +++ b/src/interactive-window/serviceRegistry.web.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. 'use strict'; -import { ITracebackFormatter } from '../kernels/types'; +import { IStartupCodeProvider, ITracebackFormatter } from '../kernels/types'; import { IExtensionSingleActivationService, IExtensionSyncActivationService } from '../platform/activation/types'; import { IServiceManager } from '../platform/ioc/types'; import { CommandRegistry } from './commands/commandRegistry'; @@ -24,6 +24,7 @@ import { IGeneratedCodeStorageFactory } from './editor-integration/types'; import { GeneratedCodeStorageManager } from './generatedCodeStoreManager'; import { InteractiveWindowTracebackFormatter } from './outputs/tracebackFormatter'; import { InteractiveWindowDebuggingManager } from './debugger/jupyter/debuggingManager'; +import { InteractiveWindowDebuggingStartupCodeProvider } from './debugger/startupCodeProvider'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IInteractiveWindowProvider, InteractiveWindowProvider); @@ -53,4 +54,8 @@ export function registerTypes(serviceManager: IServiceManager) { undefined, [IExtensionSingleActivationService] ); + serviceManager.addSingleton( + IStartupCodeProvider, + InteractiveWindowDebuggingStartupCodeProvider + ); } diff --git a/src/kernels/kernel.node.ts b/src/kernels/kernel.node.ts deleted file mode 100644 index 60f4db377ea..00000000000 --- a/src/kernels/kernel.node.ts +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; -import { NotebookController, NotebookDocument, Uri } from 'vscode'; -import { IApplicationShell, IWorkspaceService } from '../platform/common/application/types'; -import { CodeSnippets, InteractiveWindowView } from '../platform/common/constants'; -import { traceInfo, traceError } from '../platform/logging'; -import { IPythonExecutionFactory } from '../platform/common/process/types.node'; -import { Resource, IConfigurationService, IExtensionContext } from '../platform/common/types'; -import { calculateWorkingDirectory } from '../platform/common/utils.node'; -import { isLocalHostConnection, isPythonKernelConnection } from './helpers'; -import { expandWorkingDir } from './jupyter/jupyterUtils'; -import { - INotebookProvider, - isLocalConnection, - ITracebackFormatter, - KernelActionSource, - KernelConnectionMetadata -} from './types'; -import { AddRunCellHook } from '../platform/common/scriptConstants'; -import { IStatusProvider } from '../platform/progress/types'; -import { sendTelemetryForPythonKernelExecutable } from './helpers.node'; -import { BaseKernel } from './kernel.base'; -import { CellOutputDisplayIdTracker } from './execution/cellDisplayIdTracker'; -import { getFilePath } from '../platform/common/platform/fs-paths'; -import { IFileSystem } from '../platform/common/platform/types'; - -/** - * Node specific changes for a BaseKernel. - */ -export class Kernel extends BaseKernel { - constructor( - uri: Uri, - resourceUri: Resource, - notebook: NotebookDocument | undefined, - kernelConnectionMetadata: Readonly, - notebookProvider: INotebookProvider, - launchTimeout: number, - interruptTimeout: number, - appShell: IApplicationShell, - private readonly fs: IFileSystem, - controller: NotebookController, - configService: IConfigurationService, - outputTracker: CellOutputDisplayIdTracker, - workspaceService: IWorkspaceService, - private readonly pythonExecutionFactory: IPythonExecutionFactory, - statusProvider: IStatusProvider, - creator: KernelActionSource, - context: IExtensionContext, - formatters: ITracebackFormatter[] - ) { - super( - uri, - resourceUri, - notebook, - kernelConnectionMetadata, - notebookProvider, - launchTimeout, - interruptTimeout, - appShell, - controller, - configService, - workspaceService, - outputTracker, - statusProvider, - creator, - context, - formatters - ); - } - - protected async getDebugCellHook(): Promise { - const useNewDebugger = this.configService.getSettings(undefined).forceIPyKernelDebugger === true; - if (useNewDebugger) { - return []; - } - if (!isLocalConnection(this.kernelConnectionMetadata)) { - return []; - } - // Only do this for interactive windows. IPYKERNEL_CELL_NAME is set other ways in - // notebooks - if (this.notebook?.notebookType === InteractiveWindowView) { - // If using ipykernel 6, we need to set the IPYKERNEL_CELL_NAME so that - // debugging can work. However this code is harmless for IPYKERNEL 5 so just always do it - const scriptPath = AddRunCellHook.getScriptPath(this.context); - if (await this.fs.exists(scriptPath)) { - const fileContents = await this.fs.readFile(scriptPath); - return fileContents.splitLines({ trim: false }); - } - traceError(`Cannot run non-existent script file: ${scriptPath}`); - } - return []; - } - - protected async getUpdateWorkingDirectoryAndPathCode(launchingFile?: Resource): Promise { - if ( - (isLocalConnection(this.kernelConnectionMetadata) || - isLocalHostConnection(this.kernelConnectionMetadata)) && - this.kernelConnectionMetadata.kind !== 'connectToLiveRemoteKernel' // Skip for live kernel. Don't change current directory on a kernel that's already running - ) { - let suggestedDir = await calculateWorkingDirectory( - this.configService, - this.workspaceService, - this.fs, - launchingFile - ); - if (suggestedDir && (await this.fs.exists(suggestedDir))) { - traceInfo('UpdateWorkingDirectoryAndPath in Kernel'); - // We should use the launch info directory. It trumps the possible dir - return this.getChangeDirectoryCode(suggestedDir); - } else if (launchingFile && (await this.fs.exists(launchingFile))) { - // Combine the working directory with this file if possible. - suggestedDir = Uri.file( - expandWorkingDir( - getFilePath(suggestedDir), - launchingFile, - this.workspaceService, - this.configService.getSettings(launchingFile) - ) - ); - if (suggestedDir && (await this.fs.exists(suggestedDir))) { - traceInfo('UpdateWorkingDirectoryAndPath in Kernel'); - return this.getChangeDirectoryCode(suggestedDir); - } - } - } - return []; - } - - // Update both current working directory and sys.path with the desired directory - private getChangeDirectoryCode(directory: Uri): string[] { - if ( - (isLocalConnection(this.kernelConnectionMetadata) || - isLocalHostConnection(this.kernelConnectionMetadata)) && - isPythonKernelConnection(this.kernelConnectionMetadata) - ) { - return CodeSnippets.UpdateCWDAndPath.format(getFilePath(directory)).splitLines({ trim: false }); - } - return []; - } - - protected override async sendTelemetryForPythonKernelExecutable() { - if (this.session) { - return sendTelemetryForPythonKernelExecutable( - this.session, - this.resourceUri, - this.kernelConnectionMetadata, - this.pythonExecutionFactory - ); - } - } -} diff --git a/src/kernels/kernel.base.ts b/src/kernels/kernel.ts similarity index 96% rename from src/kernels/kernel.base.ts rename to src/kernels/kernel.ts index 22431091325..bc26d8385b2 100644 --- a/src/kernels/kernel.base.ts +++ b/src/kernels/kernel.ts @@ -49,6 +49,7 @@ import { INotebookProvider, InterruptResult, isLocalConnection, + IStartupCodeProvider, ITracebackFormatter, KernelActionSource, KernelConnectionMetadata, @@ -67,7 +68,7 @@ import { KernelExecution } from './execution/kernelExecution'; /** * Represents an active kernel process running on the jupyter (or local) machine. */ -export abstract class BaseKernel implements IBaseKernel { +export class Kernel implements IBaseKernel { private readonly disposables: IDisposable[] = []; get onStatusChanged(): Event { return this._onStatusChanged.event; @@ -151,12 +152,14 @@ export abstract class BaseKernel implements IBaseKernel { protected readonly appShell: IApplicationShell, public readonly controller: NotebookController, protected readonly configService: IConfigurationService, - protected readonly workspaceService: IWorkspaceService, outputTracker: CellOutputDisplayIdTracker, + protected readonly workspaceService: IWorkspaceService, private readonly statusProvider: IStatusProvider, public readonly creator: KernelActionSource, - protected readonly context: IExtensionContext, - formatters: ITracebackFormatter[] + context: IExtensionContext, + formatters: ITracebackFormatter[], + private readonly startupCodeProviders: IStartupCodeProvider[], + private readonly sendTelemetryForPythonKernelExecutable: () => Promise ) { this.kernelExecution = new KernelExecution( this, @@ -493,8 +496,6 @@ export abstract class BaseKernel implements IBaseKernel { } } - protected abstract sendTelemetryForPythonKernelExecutable(): Promise; - protected async initializeAfterStart(session: IKernelConnectionSession | undefined) { traceVerbose(`Started running kernel initialization for ${getDisplayPath(this.uri)}`); if (!session) { @@ -614,24 +615,15 @@ export abstract class BaseKernel implements IBaseKernel { // Gather all of the startup code into a giant string array so we // can execute it all at once. const result: string[] = []; - if (isPythonKernelConnection(this.kernelConnectionMetadata)) { - const [changeDirScripts, debugCellScripts] = await Promise.all([ - // Change our initial directory and path - this.getUpdateWorkingDirectoryAndPathCode(this.resourceUri), - // Initialize debug cell support. - // (IPYKERNEL_CELL_NAME has to be set on every cell execution, but we can't execute a cell to change it) - this.getDebugCellHook() - ]); - - // Have our debug cell script run first for safety - if ( - isLocalConnection(this.kernelConnectionMetadata) && - !this.configService.getSettings(undefined).forceIPyKernelDebugger - ) { - result.push(...debugCellScripts); + const dirs = await Promise.all( + this.startupCodeProviders + .sort((a, b) => b.priority - a.priority) + .map((provider) => provider.getCode(this)) + ); + for (let dir of dirs) { + result.push(...dir); } - result.push(...changeDirScripts); // Set the ipynb file const file = getFilePath(this.resourceUri); @@ -706,8 +698,6 @@ export abstract class BaseKernel implements IBaseKernel { return results; } - protected abstract getDebugCellHook(): Promise; - private getUserStartupCommands(): string[] { const settings = this.configService.getSettings(this.resourceUri); // Run any startup commands that we specified. Support the old form too @@ -726,8 +716,6 @@ export abstract class BaseKernel implements IBaseKernel { return []; } - protected abstract getUpdateWorkingDirectoryAndPathCode(launchingFile?: Resource): Promise; - private async executeSilently( session: IKernelConnectionSession | undefined, code: string[], diff --git a/src/kernels/kernel.web.ts b/src/kernels/kernel.web.ts deleted file mode 100644 index 0b14d1a309c..00000000000 --- a/src/kernels/kernel.web.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; -import { NotebookController, NotebookDocument, Uri } from 'vscode'; -import { IApplicationShell, IWorkspaceService } from '../platform/common/application/types'; -import { Resource, IConfigurationService, IExtensionContext } from '../platform/common/types'; -import { INotebookProvider, ITracebackFormatter, KernelActionSource, KernelConnectionMetadata } from './types'; -import { BaseKernel } from './kernel.base'; -import { IStatusProvider } from '../platform/progress/types'; -import { InteractiveWindowView } from '../platform/common/constants'; -import { CellOutputDisplayIdTracker } from './execution/cellDisplayIdTracker'; -import { IFileSystem } from '../platform/common/platform/types'; - -/** - * This class is just a stand in for now. It will connect to kernels in the web when this is finished. - * For now it's just here to get the service container to load. - */ -export class Kernel extends BaseKernel { - private addRunCellHookContents?: Promise; - constructor( - id: Uri, - resourceUri: Resource, - notebook: NotebookDocument | undefined, - kernelConnectionMetadata: Readonly, - notebookProvider: INotebookProvider, - launchTimeout: number, - interruptTimeout: number, - appShell: IApplicationShell, - controller: NotebookController, - configService: IConfigurationService, - outputTracker: CellOutputDisplayIdTracker, - workspaceService: IWorkspaceService, - statusProvider: IStatusProvider, - creator: KernelActionSource, - context: IExtensionContext, - formatters: ITracebackFormatter[], - private readonly fs: IFileSystem - ) { - super( - id, - resourceUri, - notebook, - kernelConnectionMetadata, - notebookProvider, - launchTimeout, - interruptTimeout, - appShell, - controller, - configService, - workspaceService, - outputTracker, - statusProvider, - creator, - context, - formatters - ); - } - - protected async getDebugCellHook(): Promise { - if (this.notebook?.notebookType === InteractiveWindowView) { - // If using ipykernel 6, we need to set the IPYKERNEL_CELL_NAME so that - // debugging can work. However this code is harmless for IPYKERNEL 5 so just always do it - if (!this.addRunCellHookContents) { - this.addRunCellHookContents = this.fs.readFile( - Uri.joinPath( - this.context.extensionUri, - 'pythonFiles', - 'vscode_datascience_helpers', - 'kernel', - 'addRunCellHook.py' - ) - ); - } - const addRunCellHook = await this.addRunCellHookContents; - return addRunCellHook.splitLines({ trim: false }); - } - return []; - } - - protected async getUpdateWorkingDirectoryAndPathCode(_launchingFile?: Resource): Promise { - // Not supported on web - return []; - } - - protected override async sendTelemetryForPythonKernelExecutable() { - // Does nothing at the moment - } -} diff --git a/src/kernels/kernelProvider.node.ts b/src/kernels/kernelProvider.node.ts index 885e76a254e..5e759222c20 100644 --- a/src/kernels/kernelProvider.node.ts +++ b/src/kernels/kernelProvider.node.ts @@ -8,17 +8,24 @@ import { IApplicationShell, IWorkspaceService, IVSCodeNotebook } from '../platfo import { IPythonExecutionFactory } from '../platform/common/process/types.node'; import { IAsyncDisposableRegistry, - IDisposableRegistry, IConfigurationService, + IDisposableRegistry, IExtensionContext } from '../platform/common/types'; -import { Kernel } from './kernel.node'; -import { IBaseKernel, IKernel, INotebookProvider, ITracebackFormatter, KernelOptions } from './types'; import { IStatusProvider } from '../platform/progress/types'; import { BaseCoreKernelProvider, BaseThirdPartyKernelProvider } from './kernelProvider.base'; import { InteractiveWindowView } from '../platform/common/constants'; import { CellOutputDisplayIdTracker } from './execution/cellDisplayIdTracker'; -import { IFileSystem } from '../platform/common/platform/types'; +import { sendTelemetryForPythonKernelExecutable } from './helpers.node'; +import { Kernel } from './kernel'; +import { + IBaseKernel, + IKernel, + INotebookProvider, + IStartupCodeProvider, + ITracebackFormatter, + KernelOptions +} from './types'; /** * Node version of a kernel provider. Needed in order to create the node version of a kernel. @@ -31,14 +38,14 @@ export class KernelProvider extends BaseCoreKernelProvider { @inject(INotebookProvider) private notebookProvider: INotebookProvider, @inject(IConfigurationService) private configService: IConfigurationService, @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IFileSystem) private readonly fs: IFileSystem, @inject(CellOutputDisplayIdTracker) private readonly outputTracker: CellOutputDisplayIdTracker, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, @inject(IVSCodeNotebook) notebook: IVSCodeNotebook, @inject(IPythonExecutionFactory) private readonly pythonExecutionFactory: IPythonExecutionFactory, @inject(IStatusProvider) private readonly statusProvider: IStatusProvider, @inject(IExtensionContext) private readonly context: IExtensionContext, - @multiInject(ITracebackFormatter) private readonly formatters: ITracebackFormatter[] + @multiInject(ITracebackFormatter) private readonly formatters: ITracebackFormatter[], + @multiInject(IStartupCodeProvider) private readonly startupCodeProviders: IStartupCodeProvider[] ) { super(asyncDisposables, disposables, notebook); } @@ -63,16 +70,27 @@ export class KernelProvider extends BaseCoreKernelProvider { waitForIdleTimeout, interruptTimeout, this.appShell, - this.fs, options.controller, this.configService, this.outputTracker, this.workspaceService, - this.pythonExecutionFactory, this.statusProvider, options.creator, this.context, - this.formatters + this.formatters, + this.startupCodeProviders, + () => { + if (kernel.session) { + return sendTelemetryForPythonKernelExecutable( + kernel.session, + kernel.resourceUri, + kernel.kernelConnectionMetadata, + this.pythonExecutionFactory + ); + } else { + return Promise.resolve(); + } + } ) as IKernel; kernel.onRestarted(() => this._onDidRestartKernel.fire(kernel), this, this.disposables); kernel.onDisposed(() => this._onDidDisposeKernel.fire(kernel), this, this.disposables); @@ -97,14 +115,14 @@ export class ThirdPartyKernelProvider extends BaseThirdPartyKernelProvider { @inject(INotebookProvider) private notebookProvider: INotebookProvider, @inject(IConfigurationService) private configService: IConfigurationService, @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IFileSystem) private readonly fs: IFileSystem, @inject(CellOutputDisplayIdTracker) private readonly outputTracker: CellOutputDisplayIdTracker, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, @inject(IVSCodeNotebook) notebook: IVSCodeNotebook, @inject(IPythonExecutionFactory) private readonly pythonExecutionFactory: IPythonExecutionFactory, @inject(IStatusProvider) private readonly statusProvider: IStatusProvider, @inject(IExtensionContext) private readonly context: IExtensionContext, - @multiInject(ITracebackFormatter) private readonly formatters: ITracebackFormatter[] + @multiInject(ITracebackFormatter) private readonly formatters: ITracebackFormatter[], + @multiInject(IStartupCodeProvider) private readonly startupCodeProviders: IStartupCodeProvider[] ) { super(asyncDisposables, disposables, notebook); } @@ -120,7 +138,7 @@ export class ThirdPartyKernelProvider extends BaseThirdPartyKernelProvider { const resourceUri = uri; const waitForIdleTimeout = this.configService.getSettings(resourceUri).jupyterLaunchTimeout; const interruptTimeout = this.configService.getSettings(resourceUri).jupyterInterruptTimeout; - const kernel = new Kernel( + let kernel: Kernel = new Kernel( uri, resourceUri, undefined, @@ -129,16 +147,27 @@ export class ThirdPartyKernelProvider extends BaseThirdPartyKernelProvider { waitForIdleTimeout, interruptTimeout, this.appShell, - this.fs, options.controller, this.configService, this.outputTracker, this.workspaceService, - this.pythonExecutionFactory, this.statusProvider, options.creator, this.context, - this.formatters + this.formatters, + this.startupCodeProviders, + () => { + if (kernel.session) { + return sendTelemetryForPythonKernelExecutable( + kernel.session, + kernel.resourceUri, + kernel.kernelConnectionMetadata, + this.pythonExecutionFactory + ); + } else { + return Promise.resolve(); + } + } ); kernel.onRestarted(() => this._onDidRestartKernel.fire(kernel), this, this.disposables); kernel.onDisposed(() => this._onDidDisposeKernel.fire(kernel), this, this.disposables); diff --git a/src/kernels/kernelProvider.web.ts b/src/kernels/kernelProvider.web.ts index 9471880ec97..617cd2a913e 100644 --- a/src/kernels/kernelProvider.web.ts +++ b/src/kernels/kernelProvider.web.ts @@ -3,21 +3,27 @@ 'use strict'; import { inject, injectable, multiInject } from 'inversify'; +import { IApplicationShell, IVSCodeNotebook, IWorkspaceService } from '../platform/common/application/types'; +import { InteractiveWindowView } from '../platform/common/constants'; import { NotebookDocument, Uri } from 'vscode'; -import { IApplicationShell, IWorkspaceService, IVSCodeNotebook } from '../platform/common/application/types'; import { IAsyncDisposableRegistry, - IDisposableRegistry, IConfigurationService, + IDisposableRegistry, IExtensionContext } from '../platform/common/types'; -import { Kernel } from './kernel.web'; -import { IBaseKernel, IKernel, INotebookProvider, ITracebackFormatter, KernelOptions } from './types'; import { BaseCoreKernelProvider, BaseThirdPartyKernelProvider } from './kernelProvider.base'; import { IStatusProvider } from '../platform/progress/types'; -import { InteractiveWindowView } from '../platform/common/constants'; import { CellOutputDisplayIdTracker } from './execution/cellDisplayIdTracker'; -import { IFileSystem } from '../platform/common/platform/types'; +import { Kernel } from './kernel'; +import { + IBaseKernel, + IKernel, + INotebookProvider, + IStartupCodeProvider, + ITracebackFormatter, + KernelOptions +} from './types'; /** * Web version of a kernel provider. Needed in order to create the web version of a kernel. @@ -36,7 +42,7 @@ export class KernelProvider extends BaseCoreKernelProvider { @inject(IStatusProvider) private readonly statusProvider: IStatusProvider, @inject(IExtensionContext) private readonly context: IExtensionContext, @multiInject(ITracebackFormatter) private readonly formatters: ITracebackFormatter[], - @inject(IFileSystem) private readonly fs: IFileSystem + @multiInject(IStartupCodeProvider) private readonly startupCodeProviders: IStartupCodeProvider[] ) { super(asyncDisposables, disposables, notebook); } @@ -69,7 +75,8 @@ export class KernelProvider extends BaseCoreKernelProvider { options.creator, this.context, this.formatters, - this.fs + this.startupCodeProviders, + () => Promise.resolve() ) as IKernel; kernel.onRestarted(() => this._onDidRestartKernel.fire(kernel), this, this.disposables); kernel.onDisposed(() => this._onDidDisposeKernel.fire(kernel), this, this.disposables); @@ -101,7 +108,7 @@ export class ThirdPartyKernelProvider extends BaseThirdPartyKernelProvider { @inject(IStatusProvider) private readonly statusProvider: IStatusProvider, @inject(IExtensionContext) private readonly context: IExtensionContext, @multiInject(ITracebackFormatter) private readonly formatters: ITracebackFormatter[], - @inject(IFileSystem) private readonly fs: IFileSystem + @multiInject(IStartupCodeProvider) private readonly startupCodeProviders: IStartupCodeProvider[] ) { super(asyncDisposables, disposables, notebook); } @@ -133,7 +140,8 @@ export class ThirdPartyKernelProvider extends BaseThirdPartyKernelProvider { options.creator, this.context, this.formatters, - this.fs + this.startupCodeProviders, + () => Promise.resolve() ); kernel.onRestarted(() => this._onDidRestartKernel.fire(kernel), this, this.disposables); kernel.onDisposed(() => this._onDidDisposeKernel.fire(kernel), this, this.disposables); diff --git a/src/kernels/kernelStartupCodeProvider.node.ts b/src/kernels/kernelStartupCodeProvider.node.ts new file mode 100644 index 00000000000..c91912fe6cd --- /dev/null +++ b/src/kernels/kernelStartupCodeProvider.node.ts @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; +import { IWorkspaceService } from '../platform/common/application/types'; +import { CodeSnippets } from '../platform/common/constants'; +import { getFilePath } from '../platform/common/platform/fs-paths'; +import { IFileSystem } from '../platform/common/platform/types'; +import { IConfigurationService } from '../platform/common/types'; +import { calculateWorkingDirectory } from '../platform/common/utils.node'; +import { traceInfo } from '../platform/logging'; +import { isLocalHostConnection, isPythonKernelConnection } from './helpers'; +import { expandWorkingDir } from './jupyter/jupyterUtils'; +import { IKernel, isLocalConnection, IStartupCodeProvider, StartupCodePriority } from './types'; + +@injectable() +export class KernelStartupCodeProvider implements IStartupCodeProvider { + public priority = StartupCodePriority.Base; + + constructor( + @inject(IConfigurationService) private readonly configService: IConfigurationService, + @inject(IFileSystem) private readonly fs: IFileSystem, + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService + ) {} + + async getCode(kernel: IKernel): Promise { + if ( + !( + isLocalConnection(kernel.kernelConnectionMetadata) && + !this.configService.getSettings(undefined).forceIPyKernelDebugger + ) + ) { + return []; + } + + if ( + isLocalConnection(kernel.kernelConnectionMetadata) || + isLocalHostConnection(kernel.kernelConnectionMetadata) + ) { + let suggestedDir = await calculateWorkingDirectory( + this.configService, + this.workspaceService, + this.fs, + kernel.resourceUri + ); + if (suggestedDir && (await this.fs.exists(suggestedDir))) { + traceInfo('UpdateWorkingDirectoryAndPath in Kernel'); + // We should use the launch info directory. It trumps the possible dir + return this.getChangeDirectoryCode(kernel, suggestedDir); + } else if (kernel.resourceUri && (await this.fs.exists(kernel.resourceUri))) { + // Combine the working directory with this file if possible. + suggestedDir = Uri.file( + expandWorkingDir( + getFilePath(suggestedDir), + kernel.resourceUri, + this.workspaceService, + this.configService.getSettings(kernel.resourceUri) + ) + ); + if (suggestedDir && (await this.fs.exists(suggestedDir))) { + traceInfo('UpdateWorkingDirectoryAndPath in Kernel'); + return this.getChangeDirectoryCode(kernel, suggestedDir); + } + } + } + return []; + } + + private getChangeDirectoryCode(kernel: IKernel, directory: Uri): string[] { + if ( + (isLocalConnection(kernel.kernelConnectionMetadata) || + isLocalHostConnection(kernel.kernelConnectionMetadata)) && + isPythonKernelConnection(kernel.kernelConnectionMetadata) + ) { + return CodeSnippets.UpdateCWDAndPath.format(getFilePath(directory)).splitLines({ trim: false }); + } + return []; + } +} diff --git a/src/kernels/serviceRegistry.node.ts b/src/kernels/serviceRegistry.node.ts index 3740136b387..cb4b22b96da 100644 --- a/src/kernels/serviceRegistry.node.ts +++ b/src/kernels/serviceRegistry.node.ts @@ -30,7 +30,13 @@ import { PreWarmActivatedJupyterEnvironmentVariables } from './variables/preWarm import { PythonVariablesRequester } from './variables/pythonVariableRequester'; import { MultiplexingDebugService } from './debugger/multiplexingDebugService'; import { IDebugLocationTracker, IDebugLocationTrackerFactory, IJupyterDebugService } from './debugger/types'; -import { IKernelDependencyService, IKernelFinder, IKernelProvider, IThirdPartyKernelProvider } from './types'; +import { + IKernelDependencyService, + IKernelFinder, + IKernelProvider, + IStartupCodeProvider, + IThirdPartyKernelProvider +} from './types'; import { IJupyterVariables, IKernelVariableRequester } from './variables/types'; import { KernelCrashMonitor } from './kernelCrashMonitor'; import { KernelAutoRestartMonitor } from './kernelAutoRestartMonitor.node'; @@ -43,6 +49,7 @@ import { DebugLocationTrackerFactory } from './debugger/debugLocationTrackerFact import { Activation } from './activation.node'; import { PortAttributesProviders } from './port/portAttributeProvider.node'; import { IServerConnectionType } from './jupyter/types'; +import { KernelStartupCodeProvider } from './kernelStartupCodeProvider.node'; export function registerTypes(serviceManager: IServiceManager, isDevMode: boolean) { serviceManager.addSingleton(IExtensionSingleActivationService, Activation); @@ -138,4 +145,5 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea serviceManager.addSingleton(IDebugLocationTracker, DebugLocationTrackerFactory, undefined, [ IDebugLocationTrackerFactory ]); + serviceManager.addSingleton(IStartupCodeProvider, KernelStartupCodeProvider); } diff --git a/src/kernels/types.ts b/src/kernels/types.ts index 167d695944c..3a5befb56a3 100644 --- a/src/kernels/types.ts +++ b/src/kernels/types.ts @@ -616,3 +616,14 @@ export interface ITracebackFormatter { */ format(cell: NotebookCell, traceback: string[]): string[]; } + +export const enum StartupCodePriority { + Base = 0, + Debugging = 5 +} + +export const IStartupCodeProvider = Symbol('IStartupCodeProvider'); +export interface IStartupCodeProvider { + priority: StartupCodePriority; + getCode(kernel: IBaseKernel): Promise; +} diff --git a/src/platform/common/scriptConstants.ts b/src/platform/common/scriptConstants.ts deleted file mode 100644 index c04c327d24c..00000000000 --- a/src/platform/common/scriptConstants.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { joinPath } from '../vscode-path/resources'; -import { IExtensionContext } from './types'; - -export namespace AddRunCellHook { - export function getScriptPath(context: IExtensionContext) { - return joinPath( - context.extensionUri, - 'pythonFiles', - 'vscode_datascience_helpers', - 'kernel', - 'addRunCellHook.py' - ); - } -} diff --git a/src/test/datascience/jupyter/kernels/installationPrompts.vscode.test.ts b/src/test/datascience/jupyter/kernels/installationPrompts.vscode.test.ts index dfae61024c2..b1603fc7e3b 100644 --- a/src/test/datascience/jupyter/kernels/installationPrompts.vscode.test.ts +++ b/src/test/datascience/jupyter/kernels/installationPrompts.vscode.test.ts @@ -9,7 +9,7 @@ import * as sinon from 'sinon'; import { commands, Memento, workspace, window, Uri, NotebookCell, NotebookDocument, NotebookCellKind } from 'vscode'; import { IPythonApiProvider } from '../../../../platform/api/types'; import { ICommandManager, IVSCodeNotebook } from '../../../../platform/common/application/types'; -import { Kernel } from '../../../../platform/../kernels/kernel.node'; +import { Kernel } from '../../../../platform/../kernels/kernel'; import { getDisplayPath } from '../../../../platform/common/platform/fs-paths'; import { GLOBAL_MEMENTO, diff --git a/src/test/kernels/kernelProvider.node.unit.test.ts b/src/test/kernels/kernelProvider.node.unit.test.ts index 4f9d715fa3a..db355e9f579 100644 --- a/src/test/kernels/kernelProvider.node.unit.test.ts +++ b/src/test/kernels/kernelProvider.node.unit.test.ts @@ -17,7 +17,6 @@ import { IApplicationShell, IVSCodeNotebook, IWorkspaceService } from '../../pla import { AsyncDisposableRegistry } from '../../platform/common/asyncDisposableRegistry'; import { JupyterNotebookView } from '../../platform/common/constants'; import { disposeAllDisposables } from '../../platform/common/helpers'; -import { IFileSystemNode } from '../../platform/common/platform/types.node'; import { IPythonExecutionFactory } from '../../platform/common/process/types.node'; import { IConfigurationService, @@ -41,7 +40,6 @@ suite('KernelProvider Node', () => { let workspaceService: IWorkspaceService; let vscNotebook: IVSCodeNotebook; let statusProvider: IStatusProvider; - let fs: IFileSystemNode; let pythonExecFactory: IPythonExecutionFactory; let context: IExtensionContext; let onDidCloseNotebookDocument: EventEmitter; @@ -78,7 +76,6 @@ suite('KernelProvider Node', () => { vscNotebook = mock(); statusProvider = mock(); context = mock(); - fs = mock(); pythonExecFactory = mock(); const configSettings = mock(); when(vscNotebook.onDidCloseNotebookDocument).thenReturn(onDidCloseNotebookDocument.event); @@ -95,13 +92,13 @@ suite('KernelProvider Node', () => { instance(notebookProvider), instance(configService), instance(appShell), - instance(fs), instance(outputTracker), instance(workspaceService), instance(vscNotebook), instance(pythonExecFactory), instance(statusProvider), instance(context), + [], [] ); thirdPartyKernelProvider = new ThirdPartyKernelProvider( @@ -110,13 +107,13 @@ suite('KernelProvider Node', () => { instance(notebookProvider), instance(configService), instance(appShell), - instance(fs), instance(outputTracker), instance(workspaceService), instance(vscNotebook), instance(pythonExecFactory), instance(statusProvider), instance(context), + [], [] ); });