diff --git a/build/ci/templates/test_phases.yml b/build/ci/templates/test_phases.yml index eb58fe041156..cdeb8841c7cb 100644 --- a/build/ci/templates/test_phases.yml +++ b/build/ci/templates/test_phases.yml @@ -118,7 +118,7 @@ steps: python -c "import sys;print(sys.executable)" displayName: 'pip install functional requirements' condition: and(succeeded(), eq(variables['NeedsPythonFunctionalReqs'], 'true')) - + # Add CONDA to the path so anaconda works # # This task will only run if variable `NeedsPythonFunctionalReqs` is true. @@ -422,7 +422,7 @@ steps: # Upload the test results to Azure DevOps to facilitate test reporting in their UX. - task: PublishTestResults@2 displayName: 'Publish functional tests results' - condition: or(contains(variables['TestsToRun'], 'testFunctional'), contains(variables['TestsToRun'], 'testParallelFunctional')) + condition: or(contains(variables['TestsToRun'], 'testFunctional'), contains(variables['TestsToRun'], 'testParallelFunctional')) inputs: testResultsFiles: '$(Build.ArtifactStagingDirectory)/test-junit*.xml' testRunTitle: 'functional-$(Agent.Os)-Py$(pythonVersion)' @@ -446,7 +446,7 @@ steps: - script: | npm run testDataScience continueOnError: true - displayName: 'Run DataScience Tests in VSCode Insiders' + displayName: 'Run Native Notebook Tests in VSCode Insiders' condition: and(succeeded(), contains(variables['TestsToRun'], 'testDataScience'), not(contains(variables['TestsToRun'], 'testDataScienceInVSCode'))) env: DISPLAY: :10 diff --git a/build/ci/vscode-python-pr-validation.yaml b/build/ci/vscode-python-pr-validation.yaml index a6f4e15fd02c..fefedc881d4d 100644 --- a/build/ci/vscode-python-pr-validation.yaml +++ b/build/ci/vscode-python-pr-validation.yaml @@ -52,7 +52,7 @@ stages: 'Single Workspace': TestsToRun: 'testSingleWorkspace' NeedsPythonTestReqs: true - 'DataScience': + 'Native Notebook': TestsToRun: 'testDataScience' NeedsPythonTestReqs: true NeedsPythonFunctionalReqs: true diff --git a/src/client/datascience/baseJupyterSession.ts b/src/client/datascience/baseJupyterSession.ts index 3324711e2da7..8c938fbf8db1 100644 --- a/src/client/datascience/baseJupyterSession.ts +++ b/src/client/datascience/baseJupyterSession.ts @@ -122,7 +122,12 @@ export abstract class BaseJupyterSession implements IJupyterSession { ); } } - + public async requestKernelInfo(): Promise { + if (!this.session) { + throw new Error('Cannot request KernelInfo, Session not initialized.'); + } + return this.session.kernel.requestKernelInfo(); + } public async changeKernel(kernelConnection: KernelConnectionMetadata, timeoutMS: number): Promise { let newSession: ISessionWithSocket | undefined; diff --git a/src/client/datascience/constants.ts b/src/client/datascience/constants.ts index ee17840a2c39..0ff4f8f4f688 100644 --- a/src/client/datascience/constants.ts +++ b/src/client/datascience/constants.ts @@ -574,6 +574,7 @@ export namespace LiveShareCommands { export const getUsableJupyterPython = 'getUsableJupyterPython'; export const executeObservable = 'executeObservable'; export const getSysInfo = 'getSysInfo'; + export const requestKernelInfo = 'requestKernelInfo'; export const serverResponse = 'serverResponse'; export const catchupRequest = 'catchupRequest'; export const syncRequest = 'synchRequest'; diff --git a/src/client/datascience/jupyter/jupyterNotebook.ts b/src/client/datascience/jupyter/jupyterNotebook.ts index d931e3a7b015..c5479a0f2521 100644 --- a/src/client/datascience/jupyter/jupyterNotebook.ts +++ b/src/client/datascience/jupyter/jupyterNotebook.ts @@ -241,7 +241,9 @@ export class JupyterNotebookBase implements INotebook { } } } - + public async requestKernelInfo(): Promise { + return this.session.requestKernelInfo(); + } public get onSessionStatusChanged(): Event { if (!this.onStatusChangedEvent) { this.onStatusChangedEvent = new EventEmitter(); diff --git a/src/client/datascience/jupyter/kernels/kernel.ts b/src/client/datascience/jupyter/kernels/kernel.ts index f5937647e2fa..3bd67148755f 100644 --- a/src/client/datascience/jupyter/kernels/kernel.ts +++ b/src/client/datascience/jupyter/kernels/kernel.ts @@ -4,6 +4,7 @@ 'use strict'; import { nbformat } from '@jupyterlab/coreutils'; +import { KernelMessage } from '@jupyterlab/services'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import * as uuid from 'uuid/v4'; @@ -18,11 +19,10 @@ import { } from 'vscode'; import { ServerStatus } from '../../../../datascience-ui/interactive-common/mainState'; import { IApplicationShell, ICommandManager, IVSCodeNotebook } from '../../../common/application/types'; -import { traceError } from '../../../common/logger'; +import { traceError, traceWarning } from '../../../common/logger'; import { IDisposableRegistry } from '../../../common/types'; import { createDeferred, Deferred } from '../../../common/utils/async'; import { noop } from '../../../common/utils/misc'; -import { IInterpreterService } from '../../../interpreter/contracts'; import { CodeSnippets } from '../../constants'; import { getDefaultNotebookContent, updateNotebookMetadata } from '../../notebookStorage/baseModel'; import { @@ -51,6 +51,10 @@ export class Kernel implements IKernel { get onDisposed(): Event { return this._onDisposed.event; } + private _info?: KernelMessage.IInfoReplyMsg['content']; + get info(): KernelMessage.IInfoReplyMsg['content'] | undefined { + return this._info; + } get status(): ServerStatus { return this.notebook?.status ?? ServerStatus.NotStarted; } @@ -79,7 +83,6 @@ export class Kernel implements IKernel { private readonly disposables: IDisposableRegistry, private readonly launchTimeout: number, commandManager: ICommandManager, - interpreterService: IInterpreterService, private readonly errorHandler: IDataScienceErrorHandler, editorProvider: INotebookEditorProvider, private readonly kernelProvider: IKernelProvider, @@ -90,12 +93,12 @@ export class Kernel implements IKernel { this.kernelExecution = new KernelExecution( kernelProvider, commandManager, - interpreterService, errorHandler, editorProvider, kernelSelectionUsage, appShell, - vscNotebook + vscNotebook, + metadata ); } public async executeCell(cell: NotebookCell): Promise { @@ -124,8 +127,9 @@ export class Kernel implements IKernel { } else { await this.validate(this.uri); const metadata = ((getDefaultNotebookContent().metadata || {}) as unknown) as nbformat.INotebookMetadata; + // Create a dummy notebook metadata & update the metadata before starting the notebook (required to ensure we fetch & start the right kernel). + // Lower layers of code below getOrCreateNotebook searches for kernels again using the metadata. updateNotebookMetadata(metadata, this.metadata); - this._notebookPromise = this.notebookProvider.getOrCreateNotebook({ identity: this.uri, resource: this.uri, @@ -232,6 +236,10 @@ export class Kernel implements IKernel { if (isPythonKernelConnection(this.metadata)) { await this.notebook.setLaunchingFile(this.uri.fsPath); } + await this.notebook + .requestKernelInfo() + .then((item) => (this._info = item.content)) + .catch(traceWarning.bind('Failed to request KernelInfo')); await this.notebook.waitForIdle(this.launchTimeout); } diff --git a/src/client/datascience/jupyter/kernels/kernelExecution.ts b/src/client/datascience/jupyter/kernels/kernelExecution.ts index b72b0069bd9a..04b1e31e3708 100644 --- a/src/client/datascience/jupyter/kernels/kernelExecution.ts +++ b/src/client/datascience/jupyter/kernels/kernelExecution.ts @@ -7,13 +7,12 @@ import { NotebookCell, NotebookCellRunState, NotebookDocument } from 'vscode'; import { IApplicationShell, ICommandManager, IVSCodeNotebook } from '../../../common/application/types'; import { IDisposable } from '../../../common/types'; import { noop } from '../../../common/utils/misc'; -import { IInterpreterService } from '../../../interpreter/contracts'; import { captureTelemetry } from '../../../telemetry'; import { Commands, Telemetry, VSCodeNativeTelemetry } from '../../constants'; import { MultiCancellationTokenSource } from '../../notebook/helpers/multiCancellationToken'; import { IDataScienceErrorHandler, INotebook, INotebookEditorProvider } from '../../types'; import { CellExecution, CellExecutionFactory } from './cellExecution'; -import type { IKernel, IKernelProvider, IKernelSelectionUsage } from './types'; +import type { IKernel, IKernelProvider, IKernelSelectionUsage, KernelConnectionMetadata } from './types'; // tslint:disable-next-line: no-var-requires no-require-imports const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'); @@ -35,12 +34,12 @@ export class KernelExecution implements IDisposable { constructor( private readonly kernelProvider: IKernelProvider, private readonly commandManager: ICommandManager, - private readonly interpreterService: IInterpreterService, errorHandler: IDataScienceErrorHandler, editorProvider: INotebookEditorProvider, readonly kernelSelectionUsage: IKernelSelectionUsage, readonly appShell: IApplicationShell, - readonly vscNotebook: IVSCodeNotebook + readonly vscNotebook: IVSCodeNotebook, + readonly metadata: Readonly ) { this.executionFactory = new CellExecutionFactory(errorHandler, editorProvider, appShell, vscNotebook); } @@ -142,15 +141,7 @@ export class KernelExecution implements IDisposable { await this.validateKernel(document); let kernel = this.kernelProvider.get(document.uri); if (!kernel) { - const activeInterpreter = await this.interpreterService.getActiveInterpreter(document.uri); - kernel = this.kernelProvider.getOrCreate(document.uri, { - metadata: { - interpreter: activeInterpreter!, - kernelModel: undefined, - kernelSpec: undefined, - kind: 'startUsingPythonInterpreter' - } - }); + kernel = this.kernelProvider.getOrCreate(document.uri, { metadata: this.metadata }); } if (!kernel) { throw new Error('Unable to create a Kernel to run cell'); diff --git a/src/client/datascience/jupyter/kernels/kernelProvider.ts b/src/client/datascience/jupyter/kernels/kernelProvider.ts index ae5b43c8f20a..762ba98fb4b9 100644 --- a/src/client/datascience/jupyter/kernels/kernelProvider.ts +++ b/src/client/datascience/jupyter/kernels/kernelProvider.ts @@ -9,7 +9,6 @@ import { Uri } from 'vscode'; import { IApplicationShell, ICommandManager, IVSCodeNotebook } from '../../../common/application/types'; import { traceInfo, traceWarning } from '../../../common/logger'; import { IAsyncDisposableRegistry, IConfigurationService, IDisposableRegistry } from '../../../common/types'; -import { IInterpreterService } from '../../../interpreter/contracts'; import { IDataScienceErrorHandler, INotebookEditorProvider, INotebookProvider } from '../../types'; import { Kernel } from './kernel'; import { KernelSelector } from './kernelSelector'; @@ -24,7 +23,6 @@ export class KernelProvider implements IKernelProvider { @inject(INotebookProvider) private notebookProvider: INotebookProvider, @inject(IConfigurationService) private configService: IConfigurationService, @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IDataScienceErrorHandler) private readonly errorHandler: IDataScienceErrorHandler, @inject(INotebookEditorProvider) private readonly editorProvider: INotebookEditorProvider, @inject(KernelSelector) private readonly kernelSelectionUsage: IKernelSelectionUsage, @@ -50,7 +48,6 @@ export class KernelProvider implements IKernelProvider { this.disposables, waitForIdleTimeout, this.commandManager, - this.interpreterService, this.errorHandler, this.editorProvider, this, diff --git a/src/client/datascience/jupyter/kernels/types.ts b/src/client/datascience/jupyter/kernels/types.ts index 832fbe4e5b60..9eb8eba35a43 100644 --- a/src/client/datascience/jupyter/kernels/types.ts +++ b/src/client/datascience/jupyter/kernels/types.ts @@ -3,7 +3,7 @@ 'use strict'; -import type { Session } from '@jupyterlab/services'; +import type { KernelMessage, Session } from '@jupyterlab/services'; import type { Observable } from 'rxjs/Observable'; import type { CancellationToken, Event, QuickPickItem, Uri } from 'vscode'; import { NotebookCell, NotebookDocument } from '../../../../../types/vscode-proposed'; @@ -130,6 +130,11 @@ export interface IKernel extends IAsyncDisposable { readonly onRestarted: Event; readonly status: ServerStatus; readonly disposed: boolean; + /** + * Kernel information, used to save in ipynb in the metadata. + * Crucial for non-python notebooks, else we save the incorrect information. + */ + readonly info?: KernelMessage.IInfoReplyMsg['content']; readonly kernelSocket: Observable; start(): Promise; interrupt(): Promise; diff --git a/src/client/datascience/jupyter/liveshare/guestJupyterNotebook.ts b/src/client/datascience/jupyter/liveshare/guestJupyterNotebook.ts index 5b5d7e97b0d1..1cec5d1686d9 100644 --- a/src/client/datascience/jupyter/liveshare/guestJupyterNotebook.ts +++ b/src/client/datascience/jupyter/liveshare/guestJupyterNotebook.ts @@ -205,6 +205,16 @@ export class GuestJupyterNotebook } } + public async requestKernelInfo(): Promise { + // This is a special case. Ask the shared server + const service = await this.waitForService(); + if (service) { + return service.request(LiveShareCommands.getSysInfo, []); + } else { + throw new Error('No LiveShare service to get KernelInfo'); + } + } + public async getCompletion( _cellCode: string, _offsetInCode: number, diff --git a/src/client/datascience/notebook/helpers/helpers.ts b/src/client/datascience/notebook/helpers/helpers.ts index b9c0ac9b3631..bdd656b35d59 100644 --- a/src/client/datascience/notebook/helpers/helpers.ts +++ b/src/client/datascience/notebook/helpers/helpers.ts @@ -27,6 +27,8 @@ import { JupyterNotebookView } from '../constants'; // tslint:disable-next-line: no-var-requires no-require-imports const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'); // tslint:disable-next-line: no-require-imports +import { KernelMessage } from '@jupyterlab/services'; +// tslint:disable-next-line: no-require-imports import cloneDeep = require('lodash/cloneDeep'); import { isUntitledFile } from '../../../common/utils/misc'; import { KernelConnectionMetadata } from '../../jupyter/kernels/types'; @@ -54,7 +56,10 @@ export function isJupyterNotebook(option: NotebookDocument | string) { } } -const kernelInformationForNotebooks = new WeakMap(); +const kernelInformationForNotebooks = new WeakMap< + NotebookDocument, + { metadata?: KernelConnectionMetadata | undefined; kernelInfo?: KernelMessage.IInfoReplyMsg['content'] } +>(); export function getNotebookMetadata(document: NotebookDocument): nbformat.INotebookMetadata | undefined { // tslint:disable-next-line: no-any @@ -70,8 +75,9 @@ export function getNotebookMetadata(document: NotebookDocument): nbformat.INoteb notebookContent = { ...content, metadata: { ...metadata, language_info } } as any; } notebookContent = cloneDeep(notebookContent); - if (kernelInformationForNotebooks.has(document)) { - updateNotebookMetadata(notebookContent.metadata, kernelInformationForNotebooks.get(document)); + const data = kernelInformationForNotebooks.get(document); + if (data && data.metadata) { + updateNotebookMetadata(notebookContent.metadata, data.metadata, data.kernelInfo); } return notebookContent.metadata; @@ -88,7 +94,20 @@ export function updateKernelInNotebookMetadata( document: NotebookDocument, kernelConnection: KernelConnectionMetadata | undefined ) { - kernelInformationForNotebooks.set(document, kernelConnection); + const data = { ...(kernelInformationForNotebooks.get(document) || {}) }; + data.metadata = kernelConnection; + kernelInformationForNotebooks.set(document, data); +} +export function updateKernelInfoInNotebookMetadata( + document: NotebookDocument, + kernelInfo: KernelMessage.IInfoReplyMsg['content'] +) { + if (kernelInformationForNotebooks.get(document)?.kernelInfo === kernelInfo) { + return; + } + const data = { ...(kernelInformationForNotebooks.get(document) || {}) }; + data.kernelInfo = kernelInfo; + kernelInformationForNotebooks.set(document, data); } /** * Converts a NotebookModel into VSCode friendly format. diff --git a/src/client/datascience/notebook/kernelProvider.ts b/src/client/datascience/notebook/kernelProvider.ts index 34f4b5e29259..fe20a3789d08 100644 --- a/src/client/datascience/notebook/kernelProvider.ts +++ b/src/client/datascience/notebook/kernelProvider.ts @@ -19,10 +19,15 @@ import { areKernelConnectionsEqual } from '../jupyter/kernels/helpers'; import { KernelSelectionProvider } from '../jupyter/kernels/kernelSelections'; import { KernelSelector } from '../jupyter/kernels/kernelSelector'; import { KernelSwitcher } from '../jupyter/kernels/kernelSwitcher'; -import { getKernelConnectionId, IKernelProvider, KernelConnectionMetadata } from '../jupyter/kernels/types'; +import { getKernelConnectionId, IKernel, IKernelProvider, KernelConnectionMetadata } from '../jupyter/kernels/types'; import { INotebookStorageProvider } from '../notebookStorage/notebookStorageProvider'; import { INotebook, INotebookProvider } from '../types'; -import { getNotebookMetadata, isJupyterNotebook, updateKernelInNotebookMetadata } from './helpers/helpers'; +import { + getNotebookMetadata, + isJupyterNotebook, + updateKernelInfoInNotebookMetadata, + updateKernelInNotebookMetadata +} from './helpers/helpers'; class VSCodeNotebookKernelMetadata implements VSCNotebookKernel { get preloads(): Uri[] { @@ -37,13 +42,22 @@ class VSCodeNotebookKernelMetadata implements VSCNotebookKernel { public readonly detail: string, public readonly selection: Readonly, public readonly isPreferred: boolean, - private readonly kernelProvider: IKernelProvider + private readonly kernelProvider: IKernelProvider, + private readonly notebook: IVSCodeNotebook ) {} - public executeCell(_: NotebookDocument, cell: NotebookCell) { - this.kernelProvider.getOrCreate(cell.notebook.uri, { metadata: this.selection })?.executeCell(cell); // NOSONAR + public executeCell(doc: NotebookDocument, cell: NotebookCell) { + const kernel = this.kernelProvider.getOrCreate(cell.notebook.uri, { metadata: this.selection }); + if (kernel) { + this.updateKernelInfoInNotebookWhenAvailable(kernel, doc); + kernel.executeCell(cell).catch(noop); + } } public executeAllCells(document: NotebookDocument) { - this.kernelProvider.getOrCreate(document.uri, { metadata: this.selection })?.executeAllCells(document); // NOSONAR + const kernel = this.kernelProvider.getOrCreate(document.uri, { metadata: this.selection }); + if (kernel) { + this.updateKernelInfoInNotebookWhenAvailable(kernel, document); + kernel.executeAllCells(document).catch(noop); + } } public cancelCellExecution(_: NotebookDocument, cell: NotebookCell) { this.kernelProvider.get(cell.notebook.uri)?.interrupt(); // NOSONAR @@ -51,6 +65,19 @@ class VSCodeNotebookKernelMetadata implements VSCNotebookKernel { public cancelAllCellsExecution(document: NotebookDocument) { this.kernelProvider.get(document.uri)?.interrupt(); // NOSONAR } + private updateKernelInfoInNotebookWhenAvailable(kernel: IKernel, doc: NotebookDocument) { + const disposable = kernel.onStatusChanged(() => { + if (!kernel.info) { + return; + } + const editor = this.notebook.notebookEditors.find((item) => item.document === doc); + if (!editor || editor.kernel?.id !== this.id) { + return; + } + disposable.dispose(); + updateKernelInfoInNotebookMetadata(doc, kernel.info); + }); + } } @injectable() @@ -114,12 +141,13 @@ export class VSCodeKernelPickerProvider implements NotebookKernelProvider { kernel.detail || '', kernel.selection, areKernelConnectionsEqual(kernel.selection, preferredKernel), - this.kernelProvider + this.kernelProvider, + this.notebook ); }); // If no preferred kernel set but we have a language, use that to set preferred instead. - if (!mapped.find((v) => v.isPreferred) && document.cells.length) { + if (!mapped.find((v) => v.isPreferred)) { const languages = document.cells.map((c) => c.language); // Find the first that matches on language const indexOfKernelMatchingDocumentLanguage = kernels.findIndex((k) => @@ -141,7 +169,8 @@ export class VSCodeKernelPickerProvider implements NotebookKernelProvider { kernel.detail || '', kernel.selection, true, - this.kernelProvider + this.kernelProvider, + this.notebook ) ); } @@ -171,7 +200,8 @@ export class VSCodeKernelPickerProvider implements NotebookKernelProvider { preferredKernel.interpreter.path, preferredKernel, true, - this.kernelProvider + this.kernelProvider, + this.notebook ); } else if (preferredKernel.kind === 'connectToLiveKernel') { return new VSCodeNotebookKernelMetadata( @@ -180,7 +210,8 @@ export class VSCodeKernelPickerProvider implements NotebookKernelProvider { preferredKernel.kernelModel.name, preferredKernel, true, - this.kernelProvider + this.kernelProvider, + this.notebook ); } else { return new VSCodeNotebookKernelMetadata( @@ -189,7 +220,8 @@ export class VSCodeKernelPickerProvider implements NotebookKernelProvider { preferredKernel.kernelSpec.name, preferredKernel, true, - this.kernelProvider + this.kernelProvider, + this.notebook ); } } diff --git a/src/client/datascience/notebookStorage/baseModel.ts b/src/client/datascience/notebookStorage/baseModel.ts index 1a4de63db0b5..8cefbb1b959d 100644 --- a/src/client/datascience/notebookStorage/baseModel.ts +++ b/src/client/datascience/notebookStorage/baseModel.ts @@ -2,7 +2,10 @@ // Licensed under the MIT License. import { nbformat } from '@jupyterlab/coreutils/lib/nbformat'; +import { KernelMessage } from '@jupyterlab/services'; +import * as fastDeepEqual from 'fast-deep-equal'; import { sha256 } from 'hash.js'; +import { cloneDeep } from 'lodash'; import { Event, EventEmitter, Memento, Uri } from 'vscode'; import { ICryptoUtils } from '../../common/types'; import { isUntitledFile } from '../../common/utils/misc'; @@ -10,6 +13,7 @@ import { pruneCell } from '../common'; import { NotebookModelChange } from '../interactive-common/interactiveWindowTypes'; import { getInterpreterFromKernelConnectionMetadata, + isPythonKernelConnection, kernelConnectionMetadataHasKernelModel } from '../jupyter/kernels/helpers'; import { KernelConnectionMetadata } from '../jupyter/kernels/types'; @@ -39,7 +43,8 @@ export function getInterpreterInfoStoredInMetadata( // tslint:disable-next-line: cyclomatic-complexity export function updateNotebookMetadata( metadata?: nbformat.INotebookMetadata, - kernelConnection?: KernelConnectionMetadata + kernelConnection?: KernelConnectionMetadata, + kernelInfo?: KernelMessage.IInfoReplyMsg['content'] ) { let changed = false; let kernelId: string | undefined; @@ -47,22 +52,33 @@ export function updateNotebookMetadata( return { changed, kernelId }; } - // Get our kernel_info and language_info from the current notebook - const interpreter = getInterpreterFromKernelConnectionMetadata(kernelConnection); - if ( - interpreter && - interpreter.version && - metadata && - metadata.language_info && - metadata.language_info.version !== interpreter.version.raw - ) { - metadata.language_info.version = interpreter.version.raw; - changed = true; - } else if (!interpreter && metadata?.language_info) { - // It's possible, such as with raw kernel and a default kernelspec to not have interpreter info - // for this case clear out old invalid language_info entries as they are related to the previous execution - metadata.language_info = undefined; - changed = true; + if (kernelInfo && kernelInfo.status === 'ok') { + if (!fastDeepEqual(metadata.language_info, kernelInfo.language_info)) { + metadata.language_info = cloneDeep(kernelInfo.language_info); + changed = true; + } + } else { + // Get our kernel_info and language_info from the current notebook + const isPythonConnection = isPythonKernelConnection(kernelConnection); + const interpreter = isPythonConnection + ? getInterpreterFromKernelConnectionMetadata(kernelConnection) + : undefined; + if ( + interpreter && + interpreter.version && + metadata && + metadata.language_info && + metadata.language_info.version !== interpreter.version.raw + ) { + metadata.language_info.version = interpreter.version.raw; + changed = true; + } else if (!interpreter && metadata?.language_info && isPythonConnection) { + // It's possible, such as with raw kernel and a default kernelspec to not have interpreter info + // for this case clear out old invalid language_info entries as they are related to the previous execution + // However we should clear previous language info only if language is python, else just leave it as is. + metadata.language_info = undefined; + changed = true; + } } const kernelSpecOrModel = diff --git a/src/client/datascience/notebookStorage/notebookStorageProvider.ts b/src/client/datascience/notebookStorage/notebookStorageProvider.ts index 48e28f048c89..702adfb9e1a4 100644 --- a/src/client/datascience/notebookStorage/notebookStorageProvider.ts +++ b/src/client/datascience/notebookStorage/notebookStorageProvider.ts @@ -90,7 +90,12 @@ export class NotebookStorageProvider implements INotebookStorageProvider { const uri = this.getNextNewNotebookUri(forVSCodeNotebooks); // Always skip loading from the hot exit file. When creating a new file we want a new file. - return this.getOrCreateModel({ file: uri, possibleContents, skipLoadingDirtyContents: true }); + return this.getOrCreateModel({ + file: uri, + possibleContents, + skipLoadingDirtyContents: true, + isNative: forVSCodeNotebooks + }); } private getNextNewNotebookUri(forVSCodeNotebooks?: boolean): Uri { diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index 5058acfc8df3..70a0e1270651 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -203,6 +203,7 @@ export interface INotebook extends IAsyncDisposable { interruptKernel(timeoutInMs: number): Promise; setLaunchingFile(file: string): Promise; getSysInfo(): Promise; + requestKernelInfo(): Promise; setMatplotLibStyle(useDark: boolean): Promise; getMatchingInterpreter(): PythonEnvironment | undefined; /** @@ -360,6 +361,7 @@ export interface IJupyterSession extends IAsyncDisposable { hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike ): void; removeMessageHook(msgId: string, hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike): void; + requestKernelInfo(): Promise; } export type ISessionWithSocket = Session.ISession & { diff --git a/src/test/datascience/mockJupyterNotebook.ts b/src/test/datascience/mockJupyterNotebook.ts index 3e98d4f6e86f..4731807e0720 100644 --- a/src/test/datascience/mockJupyterNotebook.ts +++ b/src/test/datascience/mockJupyterNotebook.ts @@ -23,6 +23,8 @@ import { PythonEnvironment } from '../../client/pythonEnvironments/info'; import { ServerStatus } from '../../datascience-ui/interactive-common/mainState'; import { noop } from '../core'; +// tslint:disable: no-any + export class MockJupyterNotebook implements INotebook { public get connection(): INotebookProviderConnection | undefined { return this.providerConnection; @@ -61,6 +63,26 @@ export class MockJupyterNotebook implements INotebook { constructor(private providerConnection: INotebookProviderConnection | undefined) { noop(); } + public async requestKernelInfo(): Promise { + return { + channel: 'shell', + content: { + protocol_version: '', + banner: '', + language_info: { + name: 'py', + version: '3' + }, + status: 'ok', + implementation: '', + implementation_version: '', + help_links: [] + }, + header: {} as any, + metadata: {} as any, + parent_header: {} as any + }; + } public registerIOPubListener(_listener: (msg: KernelMessage.IIOPubMessage, requestId: string) => void): void { noop(); } diff --git a/src/test/datascience/mockJupyterSession.ts b/src/test/datascience/mockJupyterSession.ts index a0ce1e8ab7c5..d39fca783d46 100644 --- a/src/test/datascience/mockJupyterSession.ts +++ b/src/test/datascience/mockJupyterSession.ts @@ -82,7 +82,26 @@ export class MockJupyterSession implements IJupyterSession { } return sleep(this.timedelay); } - + public async requestKernelInfo(): Promise { + return { + channel: 'shell', + content: { + protocol_version: '', + banner: '', + language_info: { + name: 'py', + version: '3' + }, + status: 'ok', + implementation: '', + implementation_version: '', + help_links: [] + }, + header: {} as any, + metadata: {} as any, + parent_header: {} as any + }; + } public prolongRestarts() { this.forceRestartTimeout = true; } diff --git a/src/test/datascience/notebook/helper.ts b/src/test/datascience/notebook/helper.ts index 1ef261f0ee91..6ce2e9c94101 100644 --- a/src/test/datascience/notebook/helper.ts +++ b/src/test/datascience/notebook/helper.ts @@ -196,8 +196,7 @@ export async function startJupyter(closeInitialEditor: boolean) { const disposables: IDisposable[] = []; try { await editorProvider.createNew(); - await deleteAllCellsAndWait(); - await insertPythonCell('print("Hello World")'); + await insertPythonCell('print("Hello World")', 0); const cell = vscodeNotebook.activeNotebookEditor!.document.cells[0]!; await executeActiveDocument(); // Wait for Jupyter to start.