mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-30 17:23:29 -05:00
Convert .NET Interactive notebook's kernel spec to a VS Code compatible kernel when saving. (#19176)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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: '',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user