Convert .NET Interactive notebook's kernel spec to a VS Code compatible kernel when saving. (#19176)

This commit is contained in:
Cory Rivera
2022-04-22 16:00:13 -07:00
committed by GitHub
parent d9cf93cdae
commit 7b58568d26
5 changed files with 211 additions and 26 deletions

View File

@@ -12,10 +12,9 @@ import { CellTypes, MimeTypes, OutputTypes } from 'sql/workbench/services/notebo
import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants';
import { NotebookCellKind } from 'vs/workbench/api/common/extHostTypes';
export const DotnetInteractiveJupyterLanguagePrefix = '.net-';
export const DotnetInteractiveLanguagePrefix = 'dotnet-interactive.';
export const DotnetInteractiveJupyterLabelPrefix = '.NET (';
export const DotnetInteractiveLabel = '.NET Interactive';
const DotnetInteractiveJupyterKernelPrefix = '.net-';
const DotnetInteractiveLanguagePrefix = 'dotnet-interactive.';
export const DotnetInteractiveDisplayName = '.NET Interactive';
export function convertToVSCodeNotebookCell(cellKind: azdata.nb.CellType, cellIndex: number, cellUri: URI, docUri: URI, cellLanguage: string, cellSource?: string | string[]): vscode.NotebookCell {
return <vscode.NotebookCell>{
@@ -139,3 +138,87 @@ export function convertToVSCodeNotebookData(notebook: azdata.nb.INotebookContent
};
return result;
}
// #region .NET Interactive Kernel Metadata Conversion
/*
Since ADS relies on notebook kernelSpecs for provider metadata in a lot of places, we have to convert
a .NET Interactive notebook's Jupyter kernelSpec to an internal representation so that it matches up with
the contributed .NET Interactive notebook provider from the Jupyter extension. When saving a notebook, we
then need to restore the original kernelSpec state so that it will work with other notebook apps like
VS Code. VS Code does something similar by shifting a Jupyter notebook's original metadata over to a new
"custom" field, which is then shifted back when saving the notebook.
This is an example of an internal kernel representation we use to get compatibility working (C#, in this case):
kernelSpec: {
name: 'jupyter-notebook', // Matches the name of the notebook provider from the Jupyter extension
language: 'dotnet-interactive.csharp', // Matches the contributed languages from the .NET Interactive extension
display_name: '.NET Interactive' // The kernel name we need to show in our dropdown to match VS Code's kernel dropdown
}
This is how that C# kernel spec would need to be saved to work in VS Code:
kernelSpec: {
name: '.net-csharp',
language: 'C#',
display_name: '.NET (C#)'
}
*/
/**
* Stores equivalent external kernel metadata in a newly created .NET Interactive notebook, which is used as the default metadata when saving the notebook. This is so that ADS notebooks are still usable in other apps.
* @param kernelSpec The notebook kernel metadata to be modified.
*/
export function addExternalInteractiveKernelMetadata(kernelSpec: azdata.nb.IKernelSpec): void {
if (kernelSpec.name === 'jupyter-notebook' && kernelSpec.display_name === DotnetInteractiveDisplayName && kernelSpec.language) {
let language = kernelSpec.language.replace(DotnetInteractiveLanguagePrefix, '');
let displayLanguage: string;
switch (language) {
case 'csharp':
displayLanguage = 'C#';
break;
case 'fsharp':
displayLanguage = 'F#';
break;
case 'pwsh':
displayLanguage = 'PowerShell';
break;
default:
displayLanguage = language;
}
if (!kernelSpec.oldName) {
kernelSpec.oldName = `${DotnetInteractiveJupyterKernelPrefix}${language}`;
}
if (!kernelSpec.oldDisplayName) {
kernelSpec.oldDisplayName = `.NET (${displayLanguage})`;
}
if (!kernelSpec.oldLanguage) {
kernelSpec.oldLanguage = displayLanguage;
}
}
}
/**
* Converts a .NET Interactive notebook's metadata to an internal representation needed for VS Code notebook compatibility. This metadata is then restored when saving the notebook.
* @param metadata The notebook metadata to be modified.
*/
export function convertToInternalInteractiveKernelMetadata(metadata: azdata.nb.INotebookMetadata | undefined): void {
if (metadata?.kernelspec?.name?.startsWith(DotnetInteractiveJupyterKernelPrefix)) {
metadata.kernelspec.oldDisplayName = metadata.kernelspec.display_name;
metadata.kernelspec.display_name = DotnetInteractiveDisplayName;
let kernelName = metadata.kernelspec.name;
let baseLanguageName = kernelName.replace(DotnetInteractiveJupyterKernelPrefix, '');
if (baseLanguageName === 'powershell') {
baseLanguageName = 'pwsh';
}
let languageName = `${DotnetInteractiveLanguagePrefix}${baseLanguageName}`;
metadata.kernelspec.oldLanguage = metadata.kernelspec.language;
metadata.kernelspec.language = languageName;
metadata.language_info.oldName = metadata.language_info.name;
metadata.language_info.name = languageName;
}
}
// #endregion

View File

@@ -7,7 +7,7 @@ import type * as vscode from 'vscode';
import type * as azdata from 'azdata';
import { ADSNotebookController } from 'sql/workbench/api/common/notebooks/adsNotebookController';
import * as nls from 'vs/nls';
import { convertToVSCodeNotebookCell } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { addExternalInteractiveKernelMetadata, convertToVSCodeNotebookCell } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { CellTypes } from 'sql/workbench/services/notebook/common/contracts';
import { VSCodeNotebookDocument } from 'sql/workbench/api/common/notebooks/vscodeNotebookDocument';
import { URI } from 'vs/base/common/uri';
@@ -86,6 +86,9 @@ class VSCodeKernel implements azdata.nb.IKernel {
this._kernelSpec.supportedLanguages = this._controller.supportedLanguages;
}
// Store external kernel names for .NET Interactive kernels for when notebook gets saved, so that notebook is usable outside of ADS
addExternalInteractiveKernelMetadata(this._kernelSpec);
this._name = this._kernelSpec.name;
this._info = {
protocol_version: '',

View File

@@ -40,7 +40,7 @@ import { LocalContentManager } from 'sql/workbench/services/notebook/common/loca
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as LanguageAssociationExtensions, ILanguageAssociationRegistry } from 'sql/workbench/services/languageAssociation/common/languageAssociation';
import { NotebookLanguage } from 'sql/workbench/common/constants';
import { DotnetInteractiveLabel, DotnetInteractiveJupyterLabelPrefix, DotnetInteractiveJupyterLanguagePrefix, DotnetInteractiveLanguagePrefix } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { convertToInternalInteractiveKernelMetadata } from 'sql/workbench/api/common/notebooks/notebookUtils';
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
const languageAssociationRegistry = Registry.as<ILanguageAssociationRegistry>(LanguageAssociationExtensions.LanguageAssociations);
@@ -561,23 +561,8 @@ export class NotebookEditorContentLoader implements IContentLoader {
}
// Special case .NET Interactive kernel spec to handle inconsistencies between notebook providers and jupyter kernel specs
if (notebookContents.metadata?.kernelspec?.display_name?.startsWith(DotnetInteractiveJupyterLabelPrefix)) {
notebookContents.metadata.kernelspec.oldDisplayName = notebookContents.metadata.kernelspec.display_name;
notebookContents.metadata.kernelspec.display_name = DotnetInteractiveLabel;
convertToInternalInteractiveKernelMetadata(notebookContents.metadata);
let kernelName = notebookContents.metadata.kernelspec.name;
let baseLanguageName = kernelName.replace(DotnetInteractiveJupyterLanguagePrefix, '');
if (baseLanguageName === 'powershell') {
baseLanguageName = 'pwsh';
}
let languageName = `${DotnetInteractiveLanguagePrefix}${baseLanguageName}`;
notebookContents.metadata.kernelspec.oldLanguage = notebookContents.metadata.kernelspec.language;
notebookContents.metadata.kernelspec.language = languageName;
notebookContents.metadata.language_info.oldName = notebookContents.metadata.language_info.name;
notebookContents.metadata.language_info.name = languageName;
}
return notebookContents;
}
}

View File

@@ -38,7 +38,7 @@ import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { AddCellEdit, CellOutputEdit, ConvertCellTypeEdit, DeleteCellEdit, MoveCellEdit, CellOutputDataEdit, SplitCellEdit } from 'sql/workbench/services/notebook/browser/models/cellEdit';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { deepClone } from 'vs/base/common/objects';
import { DotnetInteractiveLabel } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { DotnetInteractiveDisplayName } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { IPYKERNEL_DISPLAY_NAME } from 'sql/workbench/common/constants';
/*
@@ -1352,7 +1352,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
if (standardKernel) {
if (this._savedKernelInfo.name && this._savedKernelInfo.name !== standardKernel.name) {
// Special case .NET Interactive kernel name to handle inconsistencies between notebook providers and jupyter kernel specs
if (this._savedKernelInfo.display_name === DotnetInteractiveLabel) {
if (this._savedKernelInfo.display_name === DotnetInteractiveDisplayName) {
this._savedKernelInfo.oldName = this._savedKernelInfo.name;
}

View File

@@ -12,7 +12,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
import * as assert from 'assert';
import { OutputTypes } from 'sql/workbench/services/notebook/common/contracts';
import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants';
import { convertToVSCodeNotebookCell, convertToVSCodeCellOutput, convertToADSCellOutput } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { convertToVSCodeNotebookCell, convertToVSCodeCellOutput, convertToADSCellOutput, convertToInternalInteractiveKernelMetadata, addExternalInteractiveKernelMetadata } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { VSCodeNotebookDocument } from 'sql/workbench/api/common/notebooks/vscodeNotebookDocument';
import { URI } from 'vs/base/common/uri';
import { VSCodeNotebookEditor } from 'sql/workbench/api/common/notebooks/vscodeNotebookEditor';
@@ -484,5 +484,119 @@ suite('Notebook Serializer', () => {
});
});
suite('Notebook Controller', () => {
suite('.NET Interactive Kernel Metadata Conversion', async () => {
test('Convert to internal kernel metadata', async () => {
let originalMetadata: azdata.nb.INotebookMetadata = {
kernelspec: {
name: '.net-csharp',
display_name: '.NET (C#)',
language: 'C#'
},
language_info: {
name: 'C#'
}
};
let expectedCovertedMetadata: azdata.nb.INotebookMetadata = {
kernelspec: {
name: '.net-csharp',
display_name: '.NET Interactive',
language: 'dotnet-interactive.csharp',
oldDisplayName: '.NET (C#)',
oldLanguage: 'C#'
},
language_info: {
name: 'dotnet-interactive.csharp',
oldName: 'C#'
}
};
convertToInternalInteractiveKernelMetadata(originalMetadata);
assert.deepStrictEqual(originalMetadata, expectedCovertedMetadata);
});
test('Do not convert to internal metadata for non-Interactive kernels', async () => {
let originalMetadata: azdata.nb.INotebookMetadata = {
kernelspec: {
name: 'not-interactive',
display_name: '.NET (C#)',
language: 'C#'
},
language_info: {
name: 'C#'
}
};
let expectedCovertedMetadata: azdata.nb.INotebookMetadata = {
kernelspec: {
name: 'not-interactive',
display_name: '.NET (C#)',
language: 'C#'
},
language_info: {
name: 'C#'
}
};
convertToInternalInteractiveKernelMetadata(originalMetadata);
assert.deepStrictEqual(originalMetadata, expectedCovertedMetadata);
});
test('Add external kernel metadata', async () => {
let originalKernelSpec: azdata.nb.IKernelSpec = {
name: 'jupyter-notebook',
display_name: '.NET Interactive',
language: 'dotnet-interactive.csharp'
};
let expectedCovertedKernel: azdata.nb.IKernelSpec = {
name: 'jupyter-notebook',
display_name: '.NET Interactive',
language: 'dotnet-interactive.csharp',
oldName: '.net-csharp',
oldDisplayName: '.NET (C#)',
oldLanguage: 'C#'
};
addExternalInteractiveKernelMetadata(originalKernelSpec);
assert.deepStrictEqual(originalKernelSpec, expectedCovertedKernel);
});
test('Do not add external metadata to non-Interactive kernels', async () => {
// Different kernel name
let originalKernelSpec: azdata.nb.IKernelSpec = {
name: 'not-interactive',
display_name: '.NET Interactive',
language: 'dotnet-interactive.csharp'
};
let expectedCovertedKernel: azdata.nb.IKernelSpec = {
name: 'not-interactive',
display_name: '.NET Interactive',
language: 'dotnet-interactive.csharp'
};
addExternalInteractiveKernelMetadata(originalKernelSpec);
assert.deepStrictEqual(originalKernelSpec, expectedCovertedKernel);
// Different display name
originalKernelSpec = {
name: 'jupyter-notebook',
display_name: 'Not An Interactive Kernel',
language: 'dotnet-interactive.csharp'
};
expectedCovertedKernel = {
name: 'jupyter-notebook',
display_name: 'Not An Interactive Kernel',
language: 'dotnet-interactive.csharp'
};
addExternalInteractiveKernelMetadata(originalKernelSpec);
assert.deepStrictEqual(originalKernelSpec, expectedCovertedKernel);
// No language provided
originalKernelSpec = {
name: 'jupyter-notebook',
display_name: '.NET Interactive'
};
expectedCovertedKernel = {
name: 'jupyter-notebook',
display_name: '.NET Interactive'
};
addExternalInteractiveKernelMetadata(originalKernelSpec);
assert.deepStrictEqual(originalKernelSpec, expectedCovertedKernel);
});
});