Add notebook extension support for .NET Interactive. (#18334)

* Also updated kernel dropdown to only include SQL aliased kernels when using SQL notebook provider.
This commit is contained in:
Cory Rivera
2022-02-25 11:58:59 -08:00
committed by GitHub
parent 02341088eb
commit ffdefd3b52
41 changed files with 649 additions and 278 deletions

View File

@@ -102,8 +102,12 @@ export class MainThreadNotebook extends Disposable implements MainThreadNotebook
}
}
public $updateProviderDescriptionLanguages(providerId: string, languages: string[]): void {
notebookRegistry.updateProviderDescriptionLanguages(providerId, languages);
public $updateProviderKernels(providerId: string, languages: azdata.nb.IStandardKernel[]): void {
notebookRegistry.updateProviderKernels(providerId, languages);
}
public $updateKernelLanguages(providerId: string, kernelName: string, languages: string[]): void {
notebookRegistry.updateKernelLanguages(providerId, kernelName, languages);
}
//#endregion
}

View File

@@ -27,8 +27,6 @@ import { localize } from 'vs/nls';
import { IFileService } from 'vs/platform/files/common/files';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { NotebookEditor } from 'sql/workbench/contrib/notebook/browser/notebookEditor';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { NewNotebookAction } from 'sql/workbench/contrib/notebook/browser/notebookActions';
class MainThreadNotebookEditor extends Disposable {
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
@@ -322,8 +320,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
@IInstantiationService private _instantiationService: IInstantiationService,
@INotebookService private readonly _notebookService: INotebookService,
@IFileService private readonly _fileService: IFileService,
@ITextFileService private readonly _textFileService: ITextFileService,
@ICommandService private readonly _commandService: ICommandService
@ITextFileService private readonly _textFileService: ITextFileService
) {
super();
if (extHostContext) {
@@ -345,6 +342,11 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
}
}
async $tryCreateNotebookDocument(options: INotebookShowOptions): Promise<UriComponents> {
let input = await this._notebookService.createNotebookInput(options);
return input.resource;
}
$tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): Promise<string> {
return Promise.resolve(this.doOpenEditor(resource, options));
}
@@ -712,12 +714,4 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
}
});
}
$createNotebookDocument(providerId: string, contents: azdata.nb.INotebookContents): Promise<azdata.nb.NotebookDocument> {
return this._commandService.executeCommand(NewNotebookAction.INTERNAL_NEW_NOTEBOOK_CMD_ID, {
providerId: providerId,
initialContent: contents,
initialDirtyState: false
});
}
}

View File

@@ -35,13 +35,9 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
//#region APIs called by main thread
async $getSerializationManagerDetails(providerHandle: number, notebookUri: UriComponents): Promise<ISerializationManagerDetails> {
let uri = URI.revive(notebookUri);
let uriString = uri.toString();
let adapter = this.findSerializationManagerForUri(uriString);
if (!adapter) {
adapter = await this._withSerializationProvider(providerHandle, (provider) => {
return this.getOrCreateSerializationManager(provider, uri);
});
}
let adapter = await this._withSerializationProvider(providerHandle, (provider) => {
return this.getOrCreateSerializationManager(provider, uri);
});
return {
handle: adapter.handle,
@@ -50,13 +46,9 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
}
async $getExecuteManagerDetails(providerHandle: number, notebookUri: UriComponents): Promise<IExecuteManagerDetails> {
let uri = URI.revive(notebookUri);
let uriString = uri.toString();
let adapter = this.findExecuteManagerForUri(uriString);
if (!adapter) {
adapter = await this._withExecuteProvider(providerHandle, (provider) => {
return this.getOrCreateExecuteManager(provider, uri);
});
}
let adapter = await this._withExecuteProvider(providerHandle, (provider) => {
return this.getOrCreateExecuteManager(provider, uri);
});
return {
handle: adapter.handle,
@@ -66,11 +58,10 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
$handleNotebookClosed(notebookUri: UriComponents): void {
let uri = URI.revive(notebookUri);
let uriString = uri.toString();
let manager = this.findExecuteManagerForUri(uriString);
if (manager) {
this.findExecuteManagersForUri(uriString).forEach(manager => {
manager.provider.handleNotebookClosed(uri);
this._adapters.delete(manager.handle);
}
});
}
$doStartServer(managerHandle: number, kernelSpec: azdata.nb.IKernelSpec): Thenable<void> {
@@ -264,8 +255,16 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
}
createNotebookController(extension: IExtensionDescription, id: string, viewType: string, label: string, handler?: (cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, controller: vscode.NotebookController) => void | Thenable<void>, rendererScripts?: vscode.NotebookRendererScript[]): vscode.NotebookController {
let addLanguagesHandler = (id, languages) => this._proxy.$updateProviderDescriptionLanguages(id, languages);
let controller = new ADSNotebookController(extension, id, viewType, label, addLanguagesHandler, this._extHostNotebookDocumentsAndEditors, handler, extension.enableProposedApi ? rendererScripts : undefined);
let languagesHandler = (languages: string[]) => this._proxy.$updateKernelLanguages(viewType, viewType, languages);
let controller = new ADSNotebookController(extension, id, viewType, label, this._extHostNotebookDocumentsAndEditors, languagesHandler, handler, extension.enableProposedApi ? rendererScripts : undefined);
let newKernel: azdata.nb.IStandardKernel = {
name: viewType,
displayName: controller.label,
connectionProviderIds: [],
supportedLanguages: [] // These will get set later from the controller
};
this._proxy.$updateProviderKernels(viewType, [newKernel]);
let executeProvider = new VSCodeExecuteProvider(controller);
this.registerExecuteProvider(executeProvider);
return controller;
@@ -283,37 +282,29 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
return matchingAdapters;
}
private findSerializationManagerForUri(uriString: string): SerializationManagerAdapter {
for (let manager of this.getAdapters(SerializationManagerAdapter)) {
if (manager.uriString === uriString) {
return manager;
}
}
return undefined;
}
private findExecuteManagerForUri(uriString: string): ExecuteManagerAdapter {
for (let manager of this.getAdapters(ExecuteManagerAdapter)) {
if (manager.uriString === uriString) {
return manager;
}
}
return undefined;
private findExecuteManagersForUri(uriString: string): ExecuteManagerAdapter[] {
return this.getAdapters(ExecuteManagerAdapter).filter(adapter => adapter.uriString === uriString);
}
private async getOrCreateSerializationManager(provider: azdata.nb.NotebookSerializationProvider, notebookUri: URI): Promise<SerializationManagerAdapter> {
let manager = await provider.getSerializationManager(notebookUri);
let uriString = notebookUri.toString();
let adapter = new SerializationManagerAdapter(provider, manager, uriString);
adapter.handle = this._addNewAdapter(adapter);
let adapter = this.getAdapters(SerializationManagerAdapter).find(a => a.uriString === uriString && a.provider.providerId === provider.providerId);
if (!adapter) {
let manager = await provider.getSerializationManager(notebookUri);
adapter = new SerializationManagerAdapter(provider, manager, uriString);
adapter.handle = this._addNewAdapter(adapter);
}
return adapter;
}
private async getOrCreateExecuteManager(provider: azdata.nb.NotebookExecuteProvider, notebookUri: URI): Promise<ExecuteManagerAdapter> {
let manager = await provider.getExecuteManager(notebookUri);
let uriString = notebookUri.toString();
let adapter = new ExecuteManagerAdapter(provider, manager, uriString);
adapter.handle = this._addNewAdapter(adapter);
let adapter = this.getAdapters(ExecuteManagerAdapter).find(a => a.uriString === uriString && a.provider.providerId === provider.providerId);
if (!adapter) {
let manager = await provider.getExecuteManager(notebookUri);
adapter = new ExecuteManagerAdapter(provider, manager, uriString);
adapter.handle = this._addNewAdapter(adapter);
}
return adapter;
}

View File

@@ -23,6 +23,7 @@ import { ExtHostNotebookDocumentData } from 'sql/workbench/api/common/extHostNot
import { ExtHostNotebookEditor } from 'sql/workbench/api/common/extHostNotebookEditor';
import { VSCodeNotebookDocument } from 'sql/workbench/api/common/notebooks/vscodeNotebookDocument';
import { VSCodeNotebookEditor } from 'sql/workbench/api/common/notebooks/vscodeNotebookEditor';
import { docNotFoundForUriError } from 'sql/base/common/locConstants';
type Adapter = azdata.nb.NavigationProvider;
@@ -113,17 +114,19 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
if (delta.addedDocuments) {
for (const data of delta.addedDocuments) {
const resource = URI.revive(data.uri);
ok(!this._documents.has(resource.toString()), `document '${resource} already exists!'`);
const documentData = new ExtHostNotebookDocumentData(
this._proxy,
resource,
data.providerId,
data.isDirty,
data.cells
);
this._documents.set(resource.toString(), documentData);
addedDocuments.push(documentData);
// Can potentially have a document with this URI already if it was created
// separately from the notebook editor.
if (!this._documents.has(resource.toString())) {
const documentData = new ExtHostNotebookDocumentData(
this._proxy,
resource,
data.providerId,
data.isDirty,
data.cells
);
this._documents.set(resource.toString(), documentData);
addedDocuments.push(documentData);
}
}
}
@@ -220,6 +223,42 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
//#endregion
//#region Extension accessible methods
async createNotebookDocument(providerId: string, contents?: azdata.nb.INotebookContents): Promise<URI> {
let options: INotebookShowOptions = {};
if (contents) {
options.providerId = providerId;
options.initialContent = JSON.stringify(contents);
}
let uriComps = await this._proxy.$tryCreateNotebookDocument(options);
let uri = URI.revive(uriComps);
let notebookCells = contents?.cells?.map<azdata.nb.NotebookCell>(cellContents => {
return {
contents: cellContents,
uri: undefined
};
});
let documentData = new ExtHostNotebookDocumentData(
this._proxy,
uri,
providerId,
false,
notebookCells ?? []
);
this._documents.set(uri.toString(), documentData);
this._onDidOpenNotebook.fire(documentData.document);
return uri;
}
async openNotebookDocument(uri: vscode.Uri): Promise<azdata.nb.NotebookDocument> {
let docData = this._documents.get(uri.toString());
if (!docData) {
throw new Error(docNotFoundForUriError);
}
return docData.document;
}
showNotebookDocument(uri: vscode.Uri, showOptions: azdata.nb.NotebookShowOptions): Thenable<azdata.nb.NotebookEditor> {
return this.doShowNotebookDocument(uri, showOptions);
}
@@ -289,9 +328,5 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
this._adapters.delete(handle);
});
}
createNotebookDocument(providerId: string, contents: azdata.nb.INotebookContents): Promise<azdata.nb.NotebookDocument> {
return this._proxy.$createNotebookDocument(providerId, contents);
}
//#endregion
}

View File

@@ -19,6 +19,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
type SelectionChangedEvent = { selected: boolean, notebook: vscode.NotebookDocument; };
type MessageReceivedEvent = { editor: vscode.NotebookEditor, message: any; };
type ExecutionHandler = (cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, controller: vscode.NotebookController) => void | Thenable<void>;
type LanguagesHandler = (languages: string[]) => void;
type InterruptHandler = (notebook: vscode.NotebookDocument) => void | Promise<void>;
/**
@@ -39,8 +40,8 @@ export class ADSNotebookController implements vscode.NotebookController {
private _id: string,
private _viewType: string,
private _label: string,
private _addLanguagesHandler: (providerId, languages) => void,
private _extHostNotebookDocumentsAndEditors: ExtHostNotebookDocumentsAndEditors,
private _languagesHandler: LanguagesHandler,
private _handler?: ExecutionHandler,
preloads?: vscode.NotebookRendererScript[]
) {
@@ -107,7 +108,7 @@ export class ADSNotebookController implements vscode.NotebookController {
public set supportedLanguages(value: string[]) {
this._kernelData.supportedLanguages = value;
this._addLanguagesHandler(this._viewType, value);
this._languagesHandler(value);
this._languagesAdded.resolve();
}

View File

@@ -8,21 +8,30 @@ import type * as azdata from 'azdata';
import { URI } from 'vs/base/common/uri';
import { asArray } from 'vs/base/common/arrays';
import { VSBuffer } from 'vs/base/common/buffer';
import { OutputTypes } from 'sql/workbench/services/notebook/common/contracts';
import { CellTypes, MimeTypes, OutputTypes } from 'sql/workbench/services/notebook/common/contracts';
import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants';
import { NotebookCellKind } from 'vs/workbench/api/common/extHostTypes';
export function convertToVSCodeNotebookCell(cellSource: string | string[], index: number, uri: URI, language: string): vscode.NotebookCell {
export const DotnetInteractiveJupyterLanguagePrefix = '.net-';
export const DotnetInteractiveLanguagePrefix = 'dotnet-interactive.';
export const DotnetInteractiveJupyterLabelPrefix = '.NET (';
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 {
return <vscode.NotebookCell>{
index: index,
kind: cellKind === CellTypes.Code ? NotebookCellKind.Code : NotebookCellKind.Markup,
index: cellIndex,
document: <vscode.TextDocument>{
uri: uri,
languageId: language,
getText: () => Array.isArray(cellSource) ? cellSource.join('') : cellSource,
uri: cellUri,
languageId: cellLanguage,
getText: () => Array.isArray(cellSource) ? cellSource.join('') : (cellSource ?? ''),
},
notebook: <vscode.NotebookDocument>{
uri: uri
}
uri: docUri
},
outputs: [],
metadata: {},
mime: undefined
};
}
@@ -33,7 +42,7 @@ export function convertToADSCellOutput(outputs: vscode.NotebookCellOutput | vsco
outputData[item.mime] = VSBuffer.wrap(item.data).toString();
}
return {
output_type: 'execute_result',
output_type: OutputTypes.ExecuteResult,
data: outputData,
execution_count: executionOrder,
metadata: output.metadata,
@@ -59,7 +68,7 @@ export function convertToVSCodeCellOutput(output: azdata.nb.ICellOutput): vscode
case OutputTypes.Stream:
let streamOutput = output as azdata.nb.IStreamResult;
convertedOutputItems = [{
mime: 'text/html',
mime: MimeTypes.HTML,
data: VSBuffer.fromString(Array.isArray(streamOutput.text) ? streamOutput.text.join('') : streamOutput.text).buffer
}];
break;
@@ -67,7 +76,7 @@ export function convertToVSCodeCellOutput(output: azdata.nb.ICellOutput): vscode
let errorOutput = output as azdata.nb.IErrorResult;
let errorString = errorOutput.ename + ': ' + errorOutput.evalue + (errorOutput.traceback ? '\n' + errorOutput.traceback?.join('\n') : '');
convertedOutputItems = [{
mime: 'text/html',
mime: MimeTypes.HTML,
data: VSBuffer.fromString(errorString).buffer
}];
break;
@@ -79,28 +88,26 @@ export function convertToVSCodeCellOutput(output: azdata.nb.ICellOutput): vscode
};
}
export function convertToADSNotebookContents(notebookData: vscode.NotebookData): azdata.nb.INotebookContents {
export function convertToADSNotebookContents(notebookData: vscode.NotebookData | undefined): azdata.nb.INotebookContents {
let result = {
cells: notebookData.cells?.map<azdata.nb.ICellContents>(cell => {
cells: notebookData?.cells?.map<azdata.nb.ICellContents>(cell => {
let executionOrder = cell.executionSummary?.executionOrder;
return {
cell_type: cell.kind === NotebookCellKind.Code ? 'code' : 'markdown',
let convertedCell: azdata.nb.ICellContents = {
cell_type: cell.kind === NotebookCellKind.Code ? CellTypes.Code : CellTypes.Markdown,
source: cell.value,
metadata: {
language: cell.languageId
},
execution_count: executionOrder,
outputs: cell.outputs ? convertToADSCellOutput(cell.outputs, executionOrder) : undefined
};
convertedCell.metadata = cell.metadata?.custom?.metadata ?? {};
if (!convertedCell.metadata.language) {
convertedCell.metadata.language = cell.languageId;
}
return convertedCell;
}),
metadata: notebookData.metadata ?? {},
nbformat: notebookData.metadata?.custom?.nbformat ?? NBFORMAT,
nbformat_minor: notebookData.metadata?.custom?.nbformat_minor ?? NBFORMAT_MINOR
metadata: notebookData?.metadata?.custom?.metadata ?? {},
nbformat: notebookData?.metadata?.custom?.nbformat ?? NBFORMAT,
nbformat_minor: notebookData?.metadata?.custom?.nbformat_minor ?? NBFORMAT_MINOR
};
// Clear out extra lingering vscode custom metadata
delete result.metadata.custom;
return result;
}
@@ -108,20 +115,27 @@ export function convertToVSCodeNotebookData(notebook: azdata.nb.INotebookContent
let result: vscode.NotebookData = {
cells: notebook.cells?.map<vscode.NotebookCellData>(cell => {
return {
kind: cell.cell_type === 'code' ? NotebookCellKind.Code : NotebookCellKind.Markup,
value: Array.isArray(cell.source) ? cell.source.join('\n') : cell.source,
languageId: cell.metadata?.language,
kind: cell.cell_type === CellTypes.Code ? NotebookCellKind.Code : NotebookCellKind.Markup,
value: Array.isArray(cell.source) ? cell.source.join('') : cell.source,
languageId: cell.metadata?.language ?? notebook.metadata.language_info?.name,
outputs: cell.outputs?.map<vscode.NotebookCellOutput>(output => convertToVSCodeCellOutput(output)),
executionSummary: {
executionOrder: cell.execution_count
},
metadata: {
custom: {
metadata: cell.metadata
}
}
};
}),
metadata: notebook.metadata
};
result.metadata.custom = {
nbformat: notebook.nbformat,
nbformat_minor: notebook.nbformat_minor
metadata: {
custom: {
metadata: notebook.metadata,
nbformat: notebook.nbformat,
nbformat_minor: notebook.nbformat_minor
}
}
};
return result;
}

View File

@@ -8,6 +8,7 @@ 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 { CellTypes } from 'sql/workbench/services/notebook/common/contracts';
class VSCodeFuture implements azdata.nb.IFuture {
private _inProgress = true;
@@ -71,16 +72,25 @@ class VSCodeKernel implements azdata.nb.IKernel {
private readonly _info: azdata.nb.IInfoReply;
private readonly _kernelSpec: azdata.nb.IKernelSpec;
constructor(private readonly _controller: ADSNotebookController, private readonly _options: azdata.nb.ISessionOptions, language: string) {
constructor(private readonly _controller: ADSNotebookController, private readonly _options: azdata.nb.ISessionOptions) {
this._id = this._options.kernelId ?? (VSCodeKernel.kernelId++).toString();
this._name = this._options.kernelName ?? this._controller.notebookType;
this._kernelSpec = this._options.kernelSpec ?? {
name: this._controller.notebookType,
display_name: this._controller.label,
};
if (!this._kernelSpec.language) {
this._kernelSpec.language = this._controller.supportedLanguages[0];
this._kernelSpec.supportedLanguages = this._controller.supportedLanguages;
}
this._name = this._kernelSpec.name;
this._info = {
protocol_version: '',
implementation: '',
implementation_version: '',
language_info: {
name: language,
version: '',
name: this._kernelSpec.language,
oldName: this._kernelSpec.oldLanguage
},
banner: '',
help_links: [{
@@ -88,11 +98,6 @@ class VSCodeKernel implements azdata.nb.IKernel {
url: ''
}]
};
this._kernelSpec = {
name: this._name,
language: language,
display_name: this._name
};
}
public get id(): string {
@@ -123,17 +128,20 @@ class VSCodeKernel implements azdata.nb.IKernel {
return this._info;
}
public get spec(): azdata.nb.IKernelSpec {
return this._kernelSpec;
}
getSpec(): Thenable<azdata.nb.IKernelSpec> {
return Promise.resolve(this._kernelSpec);
return Promise.resolve(this.spec);
}
requestExecute(content: azdata.nb.IExecuteRequest, disposeOnDone?: boolean): azdata.nb.IFuture {
let executePromise: Promise<void>;
if (this._controller.executeHandler) {
let cell = convertToVSCodeNotebookCell(content.code, content.cellIndex, content.notebookUri, this._kernelSpec.language);
let cell = convertToVSCodeNotebookCell(CellTypes.Code, content.cellIndex, content.cellUri, content.notebookUri, content.language ?? this._kernelSpec.language, content.code);
executePromise = Promise.resolve(this._controller.executeHandler([cell], cell.notebook, this._controller));
}
else {
} else {
executePromise = Promise.resolve();
}
@@ -153,8 +161,8 @@ class VSCodeKernel implements azdata.nb.IKernel {
class VSCodeSession implements azdata.nb.ISession {
private _kernel: VSCodeKernel;
private _defaultKernelLoaded = false;
constructor(controller: ADSNotebookController, private readonly _options: azdata.nb.ISessionOptions, language: string) {
this._kernel = new VSCodeKernel(controller, this._options, language);
constructor(controller: ADSNotebookController, private readonly _options: azdata.nb.ISessionOptions) {
this._kernel = new VSCodeKernel(controller, this._options);
}
public set defaultKernelLoaded(value) {
@@ -189,10 +197,14 @@ class VSCodeSession implements azdata.nb.ISession {
return 'connected';
}
public get kernel(): azdata.nb.IKernel {
public get vsKernel(): VSCodeKernel {
return this._kernel;
}
public get kernel(): azdata.nb.IKernel {
return this.vsKernel;
}
changeKernel(kernelInfo: azdata.nb.IKernelSpec): Thenable<azdata.nb.IKernel> {
return Promise.resolve(this._kernel);
}
@@ -207,7 +219,7 @@ class VSCodeSession implements azdata.nb.ISession {
}
class VSCodeSessionManager implements azdata.nb.SessionManager {
private _sessions: azdata.nb.ISession[] = [];
private _sessions: VSCodeSession[] = [];
constructor(private readonly _controller: ADSNotebookController) {
}
@@ -221,16 +233,16 @@ class VSCodeSessionManager implements azdata.nb.SessionManager {
}
public get specs(): azdata.nb.IAllKernels {
let languages = this._controller.supportedLanguages?.length > 0 ? this._controller.supportedLanguages : [this._controller.label];
// Have to return the default kernel here, since the manager specs are accessed before kernels get added
let defaultKernel: azdata.nb.IKernelSpec = {
name: this._controller.notebookType,
language: this._controller.supportedLanguages[0],
display_name: this._controller.label,
supportedLanguages: this._controller.supportedLanguages ?? []
};
return {
defaultKernel: languages[0],
kernels: languages.map<azdata.nb.IKernelSpec>(language => {
return {
name: language,
language: language,
display_name: language
};
})
defaultKernel: defaultKernel.name,
kernels: [defaultKernel]
};
}
@@ -238,8 +250,7 @@ class VSCodeSessionManager implements azdata.nb.SessionManager {
if (!this.isReady) {
return Promise.reject(new Error(nls.localize('errorStartBeforeReady', "Cannot start a session, the manager is not yet initialized")));
}
let session: azdata.nb.ISession = new VSCodeSession(this._controller, options, this.specs.defaultKernel);
let session = new VSCodeSession(this._controller, options);
let index = this._sessions.findIndex(session => session.path === options.path);
if (index > -1) {
this._sessions.splice(index);

View File

@@ -11,7 +11,7 @@ export class VSCodeNotebookDocument implements vscode.NotebookDocument {
private readonly _convertedCells: vscode.NotebookCell[];
constructor(private readonly _notebookDoc: azdata.nb.NotebookDocument) {
this._convertedCells = this._notebookDoc.cells?.map((cell, index) => convertToVSCodeNotebookCell(cell.contents.source, index, this._notebookDoc.uri, this._notebookDoc.kernelSpec?.language));
this._convertedCells = this._notebookDoc.cells?.map((cell, index) => convertToVSCodeNotebookCell(cell.contents.cell_type, index, cell.uri, this._notebookDoc.uri, cell.contents.metadata?.language, cell.contents.source));
}
public get uri() { return this._notebookDoc.uri; }

View File

@@ -956,7 +956,8 @@ export interface MainThreadNotebookShape extends IDisposable {
$unregisterExecuteProvider(handle: number): void;
$onFutureMessage(futureId: number, type: FutureMessageType, payload: azdata.nb.IMessage): void;
$onFutureDone(futureId: number, done: INotebookFutureDone): void;
$updateProviderDescriptionLanguages(providerId: string, languages: string[]): void;
$updateProviderKernels(providerId: string, languages: azdata.nb.IStandardKernel[]): void;
$updateKernelLanguages(providerId: string, kernelName: string, languages: string[]): void;
}
export interface INotebookDocumentsAndEditorsDelta {
@@ -1011,6 +1012,7 @@ export interface ExtHostNotebookDocumentsAndEditorsShape {
export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable {
$trySetTrusted(_uri: UriComponents, isTrusted: boolean): Thenable<boolean>;
$trySaveDocument(uri: UriComponents): Thenable<boolean>;
$tryCreateNotebookDocument(options: INotebookShowOptions): Promise<UriComponents>;
$tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): Promise<string>;
$tryApplyEdits(id: string, modelVersionId: number, edits: INotebookEditOperation[], opts: IUndoStopOptions): Promise<boolean>;
$runCell(id: string, cellUri: UriComponents): Promise<boolean>;
@@ -1019,7 +1021,6 @@ export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable
$clearAllOutputs(id: string): Promise<boolean>;
$changeKernel(id: string, kernel: azdata.nb.IKernelSpec): Promise<boolean>;
$registerNavigationProvider(providerId: string, handle: number);
$createNotebookDocument(providerId: string, contents: azdata.nb.INotebookContents): Promise<azdata.nb.NotebookDocument>;
}
export interface ExtHostExtensionManagementShape {

View File

@@ -552,6 +552,19 @@ export class SqlThemeIcon {
}
}
export interface ICellMetadata {
language?: string | undefined;
tags?: string[] | undefined;
azdata_cell_guid?: string | undefined;
connection_name?: string;
/**
* .NET Interactive metadata. This is only required for compatibility with the .NET Interactive extension.
*/
dotnet_interactive?: {
language: string;
}
}
export interface ISerializationManagerDetails {
handle: number;
hasContentManager: boolean;

View File

@@ -12,7 +12,12 @@
<div #editor class="editor"></div>
</div>
</div>
<collapse-component *ngIf="cellModel.cellType === 'code' && cellModel.source && cellModel.source.length > 1" [cellModel]="cellModel" [activeCellId]="activeCellId"></collapse-component>
<div style="display: flex; flex-flow: row; justify-content: flex-end;">
<collapse-component *ngIf="cellModel.cellType === 'code' && cellModel.source && cellModel.source.length > 1" [cellModel]="cellModel" [activeCellId]="activeCellId"></collapse-component>
<div #cellLanguage class="cellLanguage" *ngIf="cellModel.cellType === 'code' && cellModel.language">
{{cellModel.displayLanguage}}
</div>
</div>
<div #parameter class="parameter" *ngIf="cellModel.cellType === 'code' && cellModel.isParameter">
<span>{{parametersText}}</span>
</div>

View File

@@ -48,6 +48,7 @@ const DEFAULT_OR_LOCAL_CONTEXT_ID = '-1';
export class CodeComponent extends CellView implements OnInit, OnChanges {
@ViewChild('toolbar', { read: ElementRef }) private toolbarElement: ElementRef;
@ViewChild('editor', { read: ElementRef }) private codeElement: ElementRef;
@ViewChild('cellLanguage', { read: ElementRef }) private languageElement: ElementRef;
public get cellModel(): ICellModel {
return this._cellModel;
@@ -265,6 +266,12 @@ export class CodeComponent extends CellView implements OnInit, OnChanges {
this.setFocusAndScroll();
}
}));
this._register(this.cellModel.onLanguageChanged(language => {
let nativeElement = <HTMLElement>this.languageElement.nativeElement;
nativeElement.innerText = this.cellModel.displayLanguage;
this.updateLanguageMode();
this._changeRef.detectChanges();
}));
this._register(this.cellModel.onCollapseStateChanged(isCollapsed => {
this.onCellCollapse(isCollapsed);
}));
@@ -379,8 +386,6 @@ export class CodeComponent extends CellView implements OnInit, OnChanges {
this.cellModel.setOverrideLanguage(magic.language);
this.updateLanguageMode();
}
} else {
this.cellModel.setOverrideLanguage(undefined);
}
}
} catch (err) {

View File

@@ -98,6 +98,10 @@ code-component .carbon-taskbar .codicon.hideIcon.execCountHundred {
margin-left: -6px;
}
code-component collapse-component {
flex-grow: 1;
}
code-component .hide-component-button {
height: 16px;
width: 100%;
@@ -106,6 +110,15 @@ code-component .hide-component-button {
background-repeat: no-repeat;
background-position: center;
background-color: transparent;
margin-top: 4px;
margin-bottom: 4px;
}
code-component .cellLanguage {
padding: 2px 15px;
display: inline-block;
text-align: center;
font-size: 16px;
}
code-component .parameter {

View File

@@ -40,6 +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';
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
const languageAssociationRegistry = Registry.as<ILanguageAssociationRegistry>(LanguageAssociationExtensions.LanguageAssociations);
@@ -351,7 +352,8 @@ export abstract class NotebookInput extends EditorInput implements INotebookInpu
connectionProviderIds: kernel.connectionProviderIds,
name: kernel.name,
displayName: kernel.displayName,
notebookProvider: kernel.notebookProvider
notebookProvider: kernel.notebookProvider,
supportedLanguages: kernel.supportedLanguages
});
});
}
@@ -540,6 +542,26 @@ export class NotebookEditorContentLoader implements IContentLoader {
async loadContent(): Promise<azdata.nb.INotebookContents> {
let notebookEditorModel = await this.notebookInput.resolve();
return this.contentManager.deserializeNotebook(notebookEditorModel.contentString);
let notebookContents = await this.contentManager.deserializeNotebook(notebookEditorModel.contentString);
// 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;
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

@@ -410,7 +410,9 @@ registerComponentType({
mimeTypes: [
'text/plain',
'application/vnd.jupyter.stdout',
'application/vnd.jupyter.stderr'
'application/vnd.jupyter.stderr',
'application/vnd.code.notebook.stdout',
'application/vnd.code.notebook.stderr'
],
rank: 120,
safe: true,
@@ -418,6 +420,19 @@ registerComponentType({
selector: MimeRendererComponent.SELECTOR
});
/**
* A mime renderer component for VS Code Notebook error data.
*/
registerComponentType({
mimeTypes: [
'application/vnd.code.notebook.error'
],
rank: 121,
safe: true,
ctor: MimeRendererComponent,
selector: MimeRendererComponent.SELECTOR
});
/**
* A placeholder component for deprecated rendered JavaScript.
*/

View File

@@ -67,7 +67,8 @@ class TestNotebookModel extends NotebookModelStub {
name: 'StandardKernel1',
displayName: 'StandardKernel1',
connectionProviderIds: ['Kernel1 connection 1', 'Kernel1 connection2'],
notebookProvider: 'kernel provider1'
notebookProvider: 'kernel provider1',
supportedLanguages: ['python']
}
],
[
@@ -76,7 +77,8 @@ class TestNotebookModel extends NotebookModelStub {
name: 'StandardKernel2',
displayName: 'StandardKernel2',
connectionProviderIds: ['Kernel1 connection 2', 'Kernel1 connection2'],
notebookProvider: 'kernel provider2'
notebookProvider: 'kernel provider2',
supportedLanguages: ['python']
}
]
]

View File

@@ -40,7 +40,8 @@ suite('Notebook Input', function (): void {
name: 'TestName',
displayName: 'TestDisplayName',
connectionProviderIds: ['TestId'],
notebookProvider: testProvider
notebookProvider: testProvider,
supportedLanguages: ['python']
}]);
});
let testManager: ISerializationManager = {
@@ -129,12 +130,14 @@ suite('Notebook Input', function (): void {
name: 'TestName1',
displayName: 'TestDisplayName1',
connectionProviderIds: ['TestId1'],
notebookProvider: 'TestProvider'
notebookProvider: 'TestProvider',
supportedLanguages: ['python']
}, {
name: 'TestName2',
displayName: 'TestDisplayName2',
connectionProviderIds: ['TestId2'],
notebookProvider: 'TestProvider'
notebookProvider: 'TestProvider',
supportedLanguages: ['python']
}];
untitledNotebookInput.standardKernels = testKernels;
assert.deepStrictEqual(untitledNotebookInput.standardKernels, testKernels);

View File

@@ -236,7 +236,8 @@ suite.skip('NotebookService:', function (): void {
standardKernels: [{
name: 'kernel1',
connectionProviderIds: [],
displayName: 'Kernel 1'
displayName: 'Kernel 1',
supportedLanguages: ['python']
}],
provider: 'otherProvider'
};

View File

@@ -21,12 +21,14 @@ suite('notebookUtils', function (): void {
const testKernel: nb.IStandardKernel = {
name: 'testName',
displayName: 'testDisplayName',
connectionProviderIds: ['testId1', 'testId2']
connectionProviderIds: ['testId1', 'testId2'],
supportedLanguages: ['python']
};
const sqlStandardKernel: nb.IStandardKernel = {
name: notebookConstants.SQL,
displayName: notebookConstants.SQL,
connectionProviderIds: [notebookConstants.SQL_CONNECTION_PROVIDER]
connectionProviderIds: [notebookConstants.SQL_CONNECTION_PROVIDER],
supportedLanguages: ['sql']
};
function setupMockNotebookService() {
@@ -108,7 +110,8 @@ suite('notebookUtils', function (): void {
name: 'testName',
displayName: 'testDisplayName',
connectionProviderIds: ['testId1', 'testId2'],
notebookProvider: 'testProvider'
notebookProvider: 'testProvider',
supportedLanguages: ['python']
}]);
});

View File

@@ -17,7 +17,7 @@ import { ConnectionProfile } from 'sql/platform/connection/common/connectionProf
import { URI, UriComponents } from 'vs/base/common/uri';
import { QueryTextEditor } from 'sql/workbench/browser/modelComponents/queryTextEditor';
import { IContextViewProvider, IDelegate } from 'vs/base/browser/ui/contextview/contextview';
import { IEditorPane } from 'vs/workbench/common/editor';
import { IEditorInput, IEditorPane } from 'vs/workbench/common/editor';
import { INotebookShowOptions } from 'sql/workbench/api/common/sqlExtHost.protocol';
import { NotebookViewsExtension } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewsExtension';
import { INotebookView, INotebookViewCell, INotebookViewMetadata, INotebookViews } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews';
@@ -235,6 +235,9 @@ export class ServerManagerStub implements nb.ServerManager {
}
export class NotebookServiceStub implements INotebookService {
createNotebookInput(options: INotebookShowOptions, resource?: UriComponents): Promise<IEditorInput> {
throw new Error('Method not implemented.');
}
_serviceBrand: undefined;
get onNotebookEditorAdd(): vsEvent.Event<INotebookEditor> {
throw new Error('Method not implemented.');

View File

@@ -33,6 +33,8 @@ import { IInsightOptions } from 'sql/workbench/common/editor/query/chartState';
import { IPosition } from 'vs/editor/common/core/position';
import { CellOutputEdit, CellOutputDataEdit } from 'sql/workbench/services/notebook/browser/models/cellEdit';
import { ILogService } from 'vs/platform/log/common/log';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ICellMetadata } from 'sql/workbench/api/common/sqlExtHostTypes';
let modelId = 0;
const ads_execute_command = 'ads_execute_command';
@@ -70,8 +72,9 @@ export class CellModel extends Disposable implements ICellModel {
private _onCellLoaded = new Emitter<string>();
private _loaded: boolean;
private _stdInVisible: boolean;
private _metadata: nb.ICellMetadata;
private _metadata: ICellMetadata;
private _isCollapsed: boolean;
private _onLanguageChanged = new Emitter<string>();
private _onCollapseStateChanged = new Emitter<boolean>();
private _modelContentChangedEvent: IModelContentChangedEvent;
private _isCommandExecutionSettingEnabled: boolean = false;
@@ -95,7 +98,8 @@ export class CellModel extends Disposable implements ICellModel {
@optional(INotebookService) private _notebookService?: INotebookService,
@optional(ICommandService) private _commandService?: ICommandService,
@optional(IConfigurationService) private _configurationService?: IConfigurationService,
@optional(ILogService) private _logService?: ILogService
@optional(ILogService) private _logService?: ILogService,
@optional(IModeService) private _modeService?: IModeService
) {
super();
this.id = `${modelId++}`;
@@ -124,6 +128,10 @@ export class CellModel extends Disposable implements ICellModel {
return other !== undefined && other.id === this.id;
}
public get onLanguageChanged(): Event<string> {
return this._onLanguageChanged.event;
}
public get onCollapseStateChanged(): Event<boolean> {
return this._onCollapseStateChanged.event;
}
@@ -385,6 +393,19 @@ export class CellModel extends Disposable implements ICellModel {
return this._options.notebook.language;
}
public get displayLanguage(): string {
let result: string;
if (this._cellType === CellTypes.Markdown) {
result = 'Markdown';
} else if (this._modeService) {
let language = this._modeService.getLanguageName(this.language);
result = language ?? this.language;
} else {
result = this.language;
}
return result;
}
public get savedConnectionName(): string | undefined {
return this._savedConnectionName;
}
@@ -394,7 +415,10 @@ export class CellModel extends Disposable implements ICellModel {
}
public setOverrideLanguage(newLanguage: string) {
this._language = newLanguage;
if (newLanguage !== this._language) {
this._language = newLanguage;
this._onLanguageChanged.fire(newLanguage);
}
}
public get onExecutionStateChange(): Event<CellExecutionState> {
@@ -616,7 +640,9 @@ export class CellModel extends Disposable implements ICellModel {
code: content,
cellIndex: this.notebookModel.findCellIndex(this),
stop_on_error: true,
notebookUri: this.notebookModel.notebookUri
notebookUri: this.notebookModel.notebookUri,
cellUri: this.cellUri,
language: this.language
}, false);
this.setFuture(future as FutureInternal);
this.fireExecutionStateChanged();
@@ -728,7 +754,7 @@ export class CellModel extends Disposable implements ICellModel {
this._future = future;
future.setReplyHandler({ handle: (msg) => this.handleReply(msg) });
future.setIOPubHandler({ handle: (msg) => this.handleIOPub(msg) });
future.setStdInHandler({ handle: (msg) => this.handleSdtIn(msg) });
future.setStdInHandler({ handle: (msg) => this.handleStdIn(msg) });
}
/**
* Clear outputs can be done as part of the "Clear Outputs" action on a cell or as part of running a cell
@@ -933,7 +959,7 @@ export class CellModel extends Disposable implements ICellModel {
* components. If one is registered the cell will call and wait on it, if not
* it will immediately return to unblock error handling
*/
private handleSdtIn(msg: nb.IStdinMessage): void | Thenable<void> {
private handleStdIn(msg: nb.IStdinMessage): void | Thenable<void> {
let handler = async () => {
if (!this._stdInHandler) {
// No-op
@@ -995,7 +1021,7 @@ export class CellModel extends Disposable implements ICellModel {
}
this._attachments = cell.attachments;
this._cellGuid = cell.metadata && cell.metadata.azdata_cell_guid ? cell.metadata.azdata_cell_guid : generateUuid();
this.setLanguageFromContents(cell);
this.setLanguageFromContents(cell.cell_type, cell.metadata);
this._savedConnectionName = this._metadata.connection_name;
if (cell.outputs) {
for (let output of cell.outputs) {
@@ -1051,13 +1077,16 @@ export class CellModel extends Disposable implements ICellModel {
this.fireOutputsChanged(false);
}
private setLanguageFromContents(cell: nb.ICellContents): void {
if (cell.cell_type === CellTypes.Markdown) {
private setLanguageFromContents(cellType: string, metadata: ICellMetadata): void {
if (cellType === CellTypes.Markdown) {
this._language = 'markdown';
} else if (cell.metadata && cell.metadata.language) {
this._language = cell.metadata.language;
} else if (metadata?.language) {
this._language = metadata.language;
} else if (metadata?.dotnet_interactive?.language) {
this._language = `dotnet-interactive.${metadata.dotnet_interactive.language}`;
} else {
this._language = this._options?.notebook?.language;
}
// else skip, we set default language anyhow
}
private addOutput(output: nb.ICellOutput) {

View File

@@ -99,25 +99,25 @@ export class ClientSession implements IClientSession {
await this._executeManager.sessionManager.ready;
}
if (this._defaultKernel) {
await this.startSessionInstance(this._defaultKernel.name);
await this.startSessionInstance(this._defaultKernel);
}
}
}
private async startSessionInstance(kernelName: string): Promise<void> {
private async startSessionInstance(kernelSpec: nb.IKernelSpec): Promise<void> {
let session: nb.ISession;
try {
// TODO #3164 should use URI instead of path for startNew
session = await this._executeManager.sessionManager.startNew({
path: this.notebookUri.fsPath,
kernelName: kernelName
// TODO add kernel name if saved in the document
kernelName: kernelSpec.name,
kernelSpec: kernelSpec
});
session.defaultKernelLoaded = true;
} catch (err) {
// TODO move registration
if (err && err.response && err.response.status === 501) {
this.options.notificationService.warn(localize('kernelRequiresConnection', "Kernel {0} was not found. The default kernel will be used instead.", kernelName));
this.options.notificationService.warn(localize('kernelRequiresConnection', "Kernel {0} was not found. The default kernel will be used instead.", kernelSpec.name));
session = await this._executeManager.sessionManager.startNew({
path: this.notebookUri.fsPath,
kernelName: undefined
@@ -128,7 +128,7 @@ export class ClientSession implements IClientSession {
}
}
this._session = session;
await this.runKernelConfigActions(kernelName);
await this.runKernelConfigActions(kernelSpec.name);
this._statusChangedEmitter.fire(session);
}
@@ -278,7 +278,7 @@ export class ClientSession implements IClientSession {
kernel = await this._session.changeKernel(options);
await this.runKernelConfigActions(kernel.name);
} else {
kernel = await this.startSessionInstance(options.name).then(() => this.kernel);
kernel = await this.startSessionInstance(options).then(() => this.kernel);
}
return kernel;
}

View File

@@ -501,6 +501,7 @@ export interface ICellModel {
cellUri: URI;
id: string;
readonly language: string;
readonly displayLanguage: string;
readonly cellGuid: string;
source: string | string[];
cellType: CellType;
@@ -530,6 +531,7 @@ export interface ICellModel {
isCollapsed: boolean;
isParameter: boolean;
isInjectedParameter: boolean;
readonly onLanguageChanged: Event<string>;
readonly onCollapseStateChanged: Event<boolean>;
readonly onParameterStateChanged: Event<boolean>;
readonly onCellModeChanged: Event<boolean>;

View File

@@ -38,6 +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';
/*
* Used to control whether a message in a dialog/wizard is displayed as an error,
@@ -1006,7 +1007,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
}
}
if (this._capabilitiesService?.providers) {
if (this._capabilitiesService?.providers && this.executeManager.providerId === SQL_NOTEBOOK_PROVIDER) {
let providers = this._capabilitiesService.providers;
for (const server in providers) {
let alias = providers[server].connection.notebookKernelAlias;
@@ -1045,6 +1046,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
this._defaultKernel = notebookConstants.sqlKernelSpec;
this._providerId = SQL_NOTEBOOK_PROVIDER;
}
if (!this._defaultLanguageInfo?.name) {
// update default language
this._defaultLanguageInfo = {
@@ -1136,12 +1138,22 @@ export class NotebookModel extends Disposable implements INotebookModel {
language = KernelsLanguage.Python;
} else if (language.toLowerCase() === 'c#') {
language = KernelsLanguage.CSharp;
} else if (language.toLowerCase() === 'f#') {
language = KernelsLanguage.FSharp;
}
} else {
language = KernelsLanguage.Python;
}
// Update cell language if it was using the previous default, but skip updating the cell
// if it was using a more specific language.
let oldLanguage = this._language;
this._language = language.toLowerCase();
this._cells?.forEach(cell => {
if (!cell.language || cell.language === oldLanguage) {
cell.setOverrideLanguage(this._language);
}
});
}
public changeKernel(displayName: string): void {
@@ -1336,6 +1348,11 @@ export class NotebookModel extends Disposable implements INotebookModel {
}
let standardKernel = this._standardKernels.find(kernel => kernel.displayName === displayName || displayName.startsWith(kernel.displayName));
if (standardKernel && 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) {
this._savedKernelInfo.oldName = this._savedKernelInfo.name;
}
this._savedKernelInfo.name = standardKernel.name;
this._savedKernelInfo.display_name = standardKernel.displayName;
}
@@ -1421,7 +1438,11 @@ export class NotebookModel extends Disposable implements INotebookModel {
this._savedKernelInfo = {
name: kernel.name,
display_name: spec.display_name,
language: spec.language
language: spec.language,
supportedLanguages: spec.supportedLanguages,
oldName: spec.oldName,
oldDisplayName: spec.oldDisplayName,
oldLanguage: spec.oldLanguage
};
this.clientSession?.configureKernel(this._savedKernelInfo);
} catch (err) {
@@ -1547,7 +1568,29 @@ export class NotebookModel extends Disposable implements INotebookModel {
let metadata = Object.create(null) as nb.INotebookMetadata;
// TODO update language and kernel when these change
metadata.kernelspec = this._savedKernelInfo;
delete metadata.kernelspec?.supportedLanguages;
metadata.language_info = this.languageInfo;
// Undo special casing for .NET Interactive
if (metadata.kernelspec?.oldName) {
metadata.kernelspec.name = metadata.kernelspec.oldName;
delete metadata.kernelspec.oldName;
}
if (metadata.kernelspec?.oldDisplayName) {
metadata.kernelspec.display_name = metadata.kernelspec.oldDisplayName;
delete metadata.kernelspec.oldDisplayName;
}
if (metadata.kernelspec?.oldLanguage) {
metadata.kernelspec.language = metadata.kernelspec.oldLanguage;
delete metadata.kernelspec.oldLanguage;
}
if (metadata.language_info?.oldName) {
metadata.language_info.name = metadata.language_info?.oldName;
delete metadata.language_info?.oldName;
}
metadata.tags = this._tags;
metadata.multi_connection_mode = this._multiConnectionMode ? this._multiConnectionMode : undefined;
if (this.configurationService.getValue(saveConnectionNameConfigName)) {

View File

@@ -59,6 +59,7 @@ export interface IStandardKernelWithProvider {
readonly displayName: string;
readonly connectionProviderIds: string[];
readonly notebookProvider: string;
readonly supportedLanguages: string[];
}
export interface IEndpoint {

View File

@@ -17,7 +17,7 @@ import { NotebookChangeType, CellType } from 'sql/workbench/services/notebook/co
import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
import { Range } from 'vs/editor/common/core/range';
import { IEditorPane } from 'vs/workbench/common/editor';
import { IEditorInput, IEditorPane } from 'vs/workbench/common/editor';
import { INotebookInput } from 'sql/workbench/services/notebook/browser/interface';
import { INotebookShowOptions } from 'sql/workbench/api/common/sqlExtHost.protocol';
import { NotebookViewsExtension } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewsExtension';
@@ -137,6 +137,8 @@ export interface INotebookService {
*/
notifyCellExecutionStarted(): void;
createNotebookInput(options: INotebookShowOptions, resource?: UriComponents): Promise<IEditorInput | undefined>;
openNotebook(resource: UriComponents, options: INotebookShowOptions): Promise<IEditorPane | undefined>;
getUntitledUriPath(originalTitle: string): string;

View File

@@ -248,13 +248,18 @@ export class NotebookService extends Disposable implements INotebookService {
lifecycleService.onWillShutdown(() => this.shutdown());
}
public async openNotebook(resource: UriComponents, options: INotebookShowOptions): Promise<IEditorPane | undefined> {
const uri = URI.revive(resource);
const editorOptions: ITextEditorOptions = {
preserveFocus: options.preserveFocus,
pinned: !options.preview
};
public async createNotebookInput(options: INotebookShowOptions, resource?: UriComponents): Promise<IEditorInput | undefined> {
let uri: URI;
if (resource) {
uri = URI.revive(resource);
} else {
// Need to create a new untitled URI, so find the lowest numbered one that's available
let counter = 1;
do {
uri = URI.from({ scheme: Schemas.untitled, path: `Notebook-${counter}` });
counter++;
} while (this._untitledEditorService.get(uri));
}
let isUntitled: boolean = uri.scheme === Schemas.untitled;
let fileInput: IEditorInput;
@@ -269,6 +274,7 @@ export class NotebookService extends Disposable implements INotebookService {
fileInput = this._editorService.createEditorInput({ forceFile: true, resource: uri, mode: 'notebook' });
}
}
// We only need to get the Notebook language association as such we only need to use ipynb
const inputCreator = languageAssociationRegistry.getAssociationForLanguage(NotebookLanguage.Ipynb);
if (inputCreator) {
@@ -286,7 +292,21 @@ export class NotebookService extends Disposable implements INotebookService {
}
}
}
return await this._editorService.openEditor(fileInput, editorOptions, viewColumnToEditorGroup(this._editorGroupService, options.position));
if (!fileInput) {
throw new Error(localize('failedToCreateNotebookInput', "Failed to create notebook input for provider '{0}'", options.providerId));
}
return fileInput;
}
public async openNotebook(resource: UriComponents, options: INotebookShowOptions): Promise<IEditorPane | undefined> {
const editorOptions: ITextEditorOptions = {
preserveFocus: options.preserveFocus,
pinned: !options.preview
};
let input = await this.createNotebookInput(options, resource);
return await this._editorService.openEditor(input, editorOptions, viewColumnToEditorGroup(this._editorGroupService, options.position));
}
/**
@@ -321,7 +341,8 @@ export class NotebookService extends Disposable implements INotebookService {
let descriptor = new StandardKernelsDescriptor(notebookConstants.SQL, [{
name: notebookConstants.SQL,
displayName: notebookConstants.SQL,
connectionProviderIds: sqlConnectionTypes
connectionProviderIds: sqlConnectionTypes,
supportedLanguages: [notebookConstants.sqlKernelSpec.language]
}]);
this._providerToStandardKernels.set(notebookConstants.SQL, descriptor);
}
@@ -785,7 +806,12 @@ export class NotebookService extends Disposable implements INotebookService {
notebookRegistry.registerProviderDescription({
provider: serializationProvider.providerId,
fileExtensions: [DEFAULT_NOTEBOOK_FILETYPE],
standardKernels: [{ name: notebookConstants.SQL, displayName: notebookConstants.SQL, connectionProviderIds: [notebookConstants.SQL_CONNECTION_PROVIDER] }]
standardKernels: [{
name: notebookConstants.SQL,
displayName: notebookConstants.SQL,
connectionProviderIds: [notebookConstants.SQL_CONNECTION_PROVIDER],
supportedLanguages: [notebookConstants.sqlKernelSpec.language]
}]
});
}

View File

@@ -55,12 +55,26 @@ export const textRendererFactory: IRenderMime.IRendererFactory = {
mimeTypes: [
'text/plain',
'application/vnd.jupyter.stdout',
'application/vnd.jupyter.stderr'
'application/vnd.jupyter.stderr',
'application/vnd.code.notebook.stdout',
'application/vnd.code.notebook.stderr'
],
defaultRank: 120,
createRenderer: options => new widgets.RenderedText(options)
};
/**
* A mime renderer factory for VS Code Notebook error data.
*/
export const errorRendererFactory: IRenderMime.IRendererFactory = {
safe: true,
mimeTypes: [
'application/vnd.code.notebook.error'
],
defaultRank: 121,
createRenderer: options => new widgets.ErrorText(options)
};
/**
* A placeholder factory for deprecated rendered JavaScript.
*/
@@ -101,6 +115,7 @@ export const standardRendererFactories: ReadonlyArray<IRenderMime.IRendererFacto
imageRendererFactory,
javaScriptRendererFactory,
textRendererFactory,
errorRendererFactory,
dataResourceRendererFactory,
ipywidgetFactory
];

View File

@@ -319,6 +319,34 @@ export class RenderedText extends RenderedCommon {
}
}
export class ErrorText extends RenderedCommon {
/**
* Construct a new error text widget.
*
* @param options - The options for initializing the widget.
*/
constructor(options: IRenderMime.IRendererOptions) {
super(options);
this.addClass('jp-ErrorText');
}
/**
* Render a mime model.
*
* @param model - The mime model to render.
*
* @returns A promise which resolves when rendering is complete.
*/
render(model: IRenderMime.IMimeModel): Promise<void> {
let err = JSON.parse(String(model.data[this.mimeType]));
let text = err.name && err.message ? `${err.name}: ${err.message}` : err.name || err.message;
return renderers.renderText({
host: this.node,
source: text
});
}
}
/**
* A widget for displaying deprecated JavaScript output.
*/

View File

@@ -22,5 +22,6 @@ export enum KernelsLanguage {
SparkScala = 'scala',
SparkR = 'sparkr',
PowerShell = 'powershell',
CSharp = 'cs'
CSharp = 'csharp',
FSharp = 'fsharp'
}

View File

@@ -55,6 +55,12 @@ let providerDescriptionType: IJSONSchema = {
items: {
type: 'string'
}
},
supportedLanguages: {
type: 'array',
items: {
type: 'string'
}
}
}
}
@@ -112,7 +118,8 @@ export interface INotebookProviderRegistry {
readonly onNewDescriptionRegistration: Event<{ id: string, registration: ProviderDescriptionRegistration }>;
updateProviderDescriptionLanguages(providerId: string, languages: string[]): void;
updateProviderKernels(providerId: string, kernels: azdata.nb.IStandardKernel[]): void;
updateKernelLanguages(providerId: string, kernelName: string, languages: string[]): void;
registerProviderDescription(provider: ProviderDescriptionRegistration): void;
registerNotebookLanguageMagic(magic: NotebookLanguageMagicRegistration): void;
}
@@ -124,24 +131,33 @@ class NotebookProviderRegistry implements INotebookProviderRegistry {
private _onNewDescriptionRegistration = new Emitter<{ id: string, registration: ProviderDescriptionRegistration }>();
public readonly onNewDescriptionRegistration: Event<{ id: string, registration: ProviderDescriptionRegistration }> = this._onNewDescriptionRegistration.event;
updateProviderDescriptionLanguages(providerId: string, languages: string[]): void {
private readonly providerNotInRegistryError = (providerId: string): string => localize('providerNotInRegistryError', "The specified provider '{0}' is not present in the notebook registry.", providerId);
updateProviderKernels(providerId: string, kernels: azdata.nb.IStandardKernel[]): void {
let registration = this._providerDescriptionRegistration.get(providerId);
if (!registration) {
throw new Error(localize('providerNotInRegistryError', "The specified provider '{0}' is not present in the notebook registry.", providerId));
throw new Error(this.providerNotInRegistryError(providerId));
}
let kernels = languages.map<azdata.nb.IStandardKernel>(language => {
return {
name: language,
displayName: language,
connectionProviderIds: []
};
});
registration.standardKernels = kernels;
// Update provider description with new info
this.registerProviderDescription(registration);
}
updateKernelLanguages(providerId: string, kernelName: string, languages: string[]): void {
let registration = this._providerDescriptionRegistration.get(providerId);
if (!registration) {
throw new Error(this.providerNotInRegistryError(providerId));
}
let kernel = registration.standardKernels?.find(kernel => kernel.name === kernelName);
if (kernel) {
kernel.supportedLanguages = languages;
}
// Update provider description with new info
this.registerProviderDescription(registration);
}
registerProviderDescription(registration: ProviderDescriptionRegistration): void {
this._providerDescriptionRegistration.set(registration.provider, registration);
this._onNewDescriptionRegistration.fire({ id: registration.provider, registration: registration });

View File

@@ -64,21 +64,23 @@ suite('Notebook Serializer', () => {
}
}],
metadata: {
kernelspec: {
name: 'python3',
display_name: 'Python 3',
language: 'python'
},
language_info: {
name: 'python',
version: '3.8.10',
mimetype: 'text/x-python',
codemirror_mode: {
name: 'ipython',
version: '3'
}
},
custom: {
metadata: {
kernelspec: {
name: 'python3',
display_name: 'Python 3',
language: 'python'
},
language_info: {
name: 'python',
version: '3.8.10',
mimetype: 'text/x-python',
codemirror_mode: {
name: 'ipython',
version: '3'
}
}
},
nbformat: NBFORMAT,
nbformat_minor: NBFORMAT_MINOR
}
@@ -161,6 +163,13 @@ suite('Notebook Serializer', () => {
}],
executionSummary: {
executionOrder: 1
},
metadata: {
custom: {
metadata: {
language: 'python'
}
}
}
}, {
kind: NotebookCellKind.Code,
@@ -176,24 +185,33 @@ suite('Notebook Serializer', () => {
}],
executionSummary: {
executionOrder: 2
},
metadata: {
custom: {
metadata: {
language: 'python'
}
}
}
}],
metadata: {
kernelspec: {
name: 'python3',
display_name: 'Python 3',
language: 'python'
},
language_info: {
name: 'python',
version: '3.8.10',
mimetype: 'text/x-python',
codemirror_mode: {
name: 'ipython',
version: '3'
}
},
custom: {
metadata: {
kernelspec: {
name: 'python3',
display_name: 'Python 3',
language: 'python'
},
language_info: {
name: 'python',
version: '3.8.10',
mimetype: 'text/x-python',
codemirror_mode: {
name: 'ipython',
version: '3'
}
}
},
nbformat: NBFORMAT,
nbformat_minor: NBFORMAT_MINOR
}
@@ -349,7 +367,10 @@ suite('Notebook Serializer', () => {
cells: [{
contents: {
cell_type: 'code',
source: '1+1'
source: '1+1',
metadata: {
language: 'python'
}
}
}, {
contents: {
@@ -413,7 +434,7 @@ suite('Notebook Serializer', () => {
}
test('Retrieve range of cells from VS Code NotebookDocument', async () => {
let expectedCells: vscode.NotebookCell[] = testDoc.cells.map((cell, index) => convertToVSCodeNotebookCell(cell.contents.source, index, testDoc.uri, testDoc.kernelSpec.language));
let expectedCells: vscode.NotebookCell[] = testDoc.cells.map((cell, index) => convertToVSCodeNotebookCell(cell.contents.cell_type, index, cell.uri, testDoc.uri, cell.contents.metadata?.language, cell.contents.source));
let vsDoc = new VSCodeNotebookDocument(testDoc);
let actualCells = vsDoc.getCells();
@@ -430,7 +451,7 @@ suite('Notebook Serializer', () => {
});
test('Retrieve specific cell from VS Code NotebookDocument', async () => {
let expectedCells: vscode.NotebookCell[] = testDoc.cells.map((cell, index) => convertToVSCodeNotebookCell(cell.contents.source, index, testDoc.uri, testDoc.kernelSpec.language));
let expectedCells: vscode.NotebookCell[] = testDoc.cells.map((cell, index) => convertToVSCodeNotebookCell(cell.contents.cell_type, index, cell.uri, testDoc.uri, cell.contents.metadata?.language, cell.contents.source));
let vsDoc = new VSCodeNotebookDocument(testDoc);
let firstCell = vsDoc.cellAt(0);