mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -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 { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants';
|
||||||
import { NotebookCellKind } from 'vs/workbench/api/common/extHostTypes';
|
import { NotebookCellKind } from 'vs/workbench/api/common/extHostTypes';
|
||||||
|
|
||||||
export const DotnetInteractiveJupyterLanguagePrefix = '.net-';
|
const DotnetInteractiveJupyterKernelPrefix = '.net-';
|
||||||
export const DotnetInteractiveLanguagePrefix = 'dotnet-interactive.';
|
const DotnetInteractiveLanguagePrefix = 'dotnet-interactive.';
|
||||||
export const DotnetInteractiveJupyterLabelPrefix = '.NET (';
|
export const DotnetInteractiveDisplayName = '.NET Interactive';
|
||||||
export const DotnetInteractiveLabel = '.NET Interactive';
|
|
||||||
|
|
||||||
export function convertToVSCodeNotebookCell(cellKind: azdata.nb.CellType, cellIndex: number, cellUri: URI, docUri: URI, cellLanguage: string, cellSource?: string | string[]): vscode.NotebookCell {
|
export function convertToVSCodeNotebookCell(cellKind: azdata.nb.CellType, cellIndex: number, cellUri: URI, docUri: URI, cellLanguage: string, cellSource?: string | string[]): vscode.NotebookCell {
|
||||||
return <vscode.NotebookCell>{
|
return <vscode.NotebookCell>{
|
||||||
@@ -139,3 +138,87 @@ export function convertToVSCodeNotebookData(notebook: azdata.nb.INotebookContent
|
|||||||
};
|
};
|
||||||
return result;
|
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 type * as azdata from 'azdata';
|
||||||
import { ADSNotebookController } from 'sql/workbench/api/common/notebooks/adsNotebookController';
|
import { ADSNotebookController } from 'sql/workbench/api/common/notebooks/adsNotebookController';
|
||||||
import * as nls from 'vs/nls';
|
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 { CellTypes } from 'sql/workbench/services/notebook/common/contracts';
|
||||||
import { VSCodeNotebookDocument } from 'sql/workbench/api/common/notebooks/vscodeNotebookDocument';
|
import { VSCodeNotebookDocument } from 'sql/workbench/api/common/notebooks/vscodeNotebookDocument';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
@@ -86,6 +86,9 @@ class VSCodeKernel implements azdata.nb.IKernel {
|
|||||||
this._kernelSpec.supportedLanguages = this._controller.supportedLanguages;
|
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._name = this._kernelSpec.name;
|
||||||
this._info = {
|
this._info = {
|
||||||
protocol_version: '',
|
protocol_version: '',
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import { LocalContentManager } from 'sql/workbench/services/notebook/common/loca
|
|||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { Extensions as LanguageAssociationExtensions, ILanguageAssociationRegistry } from 'sql/workbench/services/languageAssociation/common/languageAssociation';
|
import { Extensions as LanguageAssociationExtensions, ILanguageAssociationRegistry } from 'sql/workbench/services/languageAssociation/common/languageAssociation';
|
||||||
import { NotebookLanguage } from 'sql/workbench/common/constants';
|
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>;
|
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
|
||||||
const languageAssociationRegistry = Registry.as<ILanguageAssociationRegistry>(LanguageAssociationExtensions.LanguageAssociations);
|
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
|
// Special case .NET Interactive kernel spec to handle inconsistencies between notebook providers and jupyter kernel specs
|
||||||
if (notebookContents.metadata?.kernelspec?.display_name?.startsWith(DotnetInteractiveJupyterLabelPrefix)) {
|
convertToInternalInteractiveKernelMetadata(notebookContents.metadata);
|
||||||
notebookContents.metadata.kernelspec.oldDisplayName = notebookContents.metadata.kernelspec.display_name;
|
|
||||||
notebookContents.metadata.kernelspec.display_name = DotnetInteractiveLabel;
|
|
||||||
|
|
||||||
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;
|
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 { AddCellEdit, CellOutputEdit, ConvertCellTypeEdit, DeleteCellEdit, MoveCellEdit, CellOutputDataEdit, SplitCellEdit } from 'sql/workbench/services/notebook/browser/models/cellEdit';
|
||||||
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
|
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
|
||||||
import { deepClone } from 'vs/base/common/objects';
|
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';
|
import { IPYKERNEL_DISPLAY_NAME } from 'sql/workbench/common/constants';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1352,7 +1352,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
if (standardKernel) {
|
if (standardKernel) {
|
||||||
if (this._savedKernelInfo.name && this._savedKernelInfo.name !== standardKernel.name) {
|
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
|
// 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;
|
this._savedKernelInfo.oldName = this._savedKernelInfo.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
|
|||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { OutputTypes } from 'sql/workbench/services/notebook/common/contracts';
|
import { OutputTypes } from 'sql/workbench/services/notebook/common/contracts';
|
||||||
import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants';
|
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 { VSCodeNotebookDocument } from 'sql/workbench/api/common/notebooks/vscodeNotebookDocument';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { VSCodeNotebookEditor } from 'sql/workbench/api/common/notebooks/vscodeNotebookEditor';
|
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