diff --git a/extensions/notebook-test/.vscode/launch.json b/extensions/notebook-test/.vscode/launch.json
new file mode 100644
index 00000000000..7c2f6f64689
--- /dev/null
+++ b/extensions/notebook-test/.vscode/launch.json
@@ -0,0 +1,20 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Run Extension",
+ "type": "extensionHost",
+ "request": "launch",
+ "runtimeExecutable": "${execPath}",
+ "args": [
+ "--extensionDevelopmentPath=${workspaceFolder}"
+ ],
+ "outFiles": [
+ "${workspaceFolder}/out/**/*.js"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/extensions/notebook-test/README.md b/extensions/notebook-test/README.md
new file mode 100644
index 00000000000..b44bd72341a
--- /dev/null
+++ b/extensions/notebook-test/README.md
@@ -0,0 +1 @@
+# Notebook test
\ No newline at end of file
diff --git a/extensions/notebook-test/extension.webpack.config.js b/extensions/notebook-test/extension.webpack.config.js
new file mode 100644
index 00000000000..de88398eca0
--- /dev/null
+++ b/extensions/notebook-test/extension.webpack.config.js
@@ -0,0 +1,20 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+//@ts-check
+
+'use strict';
+
+const withDefaults = require('../shared.webpack.config');
+
+module.exports = withDefaults({
+ context: __dirname,
+ resolve: {
+ mainFields: ['module', 'main']
+ },
+ entry: {
+ extension: './src/extension.ts',
+ }
+});
diff --git a/extensions/notebook-test/icon.png b/extensions/notebook-test/icon.png
new file mode 100644
index 00000000000..64dcf7d463c
Binary files /dev/null and b/extensions/notebook-test/icon.png differ
diff --git a/extensions/notebook-test/icon.svg b/extensions/notebook-test/icon.svg
new file mode 100644
index 00000000000..0eb0f4c103b
--- /dev/null
+++ b/extensions/notebook-test/icon.svg
@@ -0,0 +1,18 @@
+
+
+
diff --git a/extensions/notebook-test/package.json b/extensions/notebook-test/package.json
new file mode 100644
index 00000000000..2fe2d7a979c
--- /dev/null
+++ b/extensions/notebook-test/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "notebook-test",
+ "displayName": "Notebook Test",
+ "description": "Notebook test for execution and outputs",
+ "extensionKind": [
+ "ui",
+ "workspace"
+ ],
+ "version": "1.0.0",
+ "publisher": "vscode",
+ "icon": "icon.png",
+ "enableProposedApi": true,
+ "license": "MIT",
+ "engines": {
+ "vscode": "^1.40.0"
+ },
+ "main": "./out/extension",
+ "categories": [
+ "Other"
+ ],
+ "activationEvents": [
+ "*"
+ ],
+ "contributes": {
+ "notebookProvider": [
+ {
+ "viewType": "jupyter",
+ "displayName": "Jupyter",
+ "selector": [
+ {
+ "filenamePattern": "*.test.ipynb"
+ }
+ ]
+ }
+ ]
+ },
+ "scripts": {
+ "compile": "tsc -p ./",
+ "watch": "tsc -watch -p ./"
+ },
+ "dependencies": {
+ "vscode-extension-telemetry": "0.1.1",
+ "vscode-nls": "^4.0.0",
+ "typescript": "^3.6.4"
+ }
+}
diff --git a/extensions/notebook-test/src/extension.ts b/extensions/notebook-test/src/extension.ts
new file mode 100644
index 00000000000..f235e315ebe
--- /dev/null
+++ b/extensions/notebook-test/src/extension.ts
@@ -0,0 +1,14 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { NotebookProvider } from './notebookProvider';
+
+export function activate(context: vscode.ExtensionContext) {
+ console.log(context.extensionPath);
+
+ context.subscriptions.push(vscode.window.registerNotebookProvider('jupyter', new NotebookProvider()));
+}
+
diff --git a/extensions/notebook-test/src/notebookProvider.ts b/extensions/notebook-test/src/notebookProvider.ts
new file mode 100644
index 00000000000..227374bae93
--- /dev/null
+++ b/extensions/notebook-test/src/notebookProvider.ts
@@ -0,0 +1,156 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+
+export class Cell implements vscode.ICell {
+ public outputs: any[] = [];
+
+ constructor(
+ public source: string[],
+ public cell_type: 'markdown' | 'code',
+ private _outputs: any[]
+ ) {
+
+ }
+
+ fillInOutputs() {
+ this.outputs = this._outputs;
+ }
+}
+
+export class JupyterNotebook implements vscode.INotebook {
+ constructor(
+ public metadata: vscode.IMetadata,
+ public cells: Cell[]
+ ) {
+
+ }
+}
+
+export class NotebookProvider implements vscode.NotebookProvider {
+ private _onDidChangeNotebook = new vscode.EventEmitter<{ resource: vscode.Uri; notebook: vscode.INotebook; }>();
+ onDidChangeNotebook: vscode.Event<{ resource: vscode.Uri; notebook: vscode.INotebook; }> = this._onDidChangeNotebook.event;
+ private _notebook: JupyterNotebook;
+ private _notebooks: Map = new Map();
+
+ constructor() {
+ this._notebook = new JupyterNotebook(
+ {
+ language_info: {
+ file_extension: 'ipynb'
+ }
+ },
+ [
+ new Cell([
+ '# header\n',
+ 'body\n'
+ ],
+ 'markdown',
+ []
+ ),
+ new Cell([
+ 'print(a)',
+ ],
+ 'code',
+ [
+ {
+ 'output_type': 'stream',
+ 'name': 'stdout',
+ 'text': 'hi, stdout\n'
+ }
+ ]
+ ),
+ new Cell(
+ [
+ 'import time, sys\n',
+ 'for i in range(8):\n',
+ ' print(i)\n',
+ ' time.sleep(0.5)'
+ ],
+ 'code',
+ [
+ {
+ 'name': 'stdout',
+ 'text': '0\n',
+ 'output_type': 'stream'
+ },
+ {
+ 'name': 'stdout',
+ 'text': '1\n',
+ 'output_type': 'stream'
+ },
+ {
+ 'name': 'stdout',
+ 'text': '2\n',
+ 'output_type': 'stream'
+ },
+ {
+ 'name': 'stdout',
+ 'text': '3\n',
+ 'output_type': 'stream'
+ },
+ {
+ 'name': 'stdout',
+ 'text': '4\n',
+ 'output_type': 'stream'
+ },
+ {
+ 'name': 'stdout',
+ 'text': '5\n',
+ 'output_type': 'stream'
+ },
+ {
+ 'name': 'stdout',
+ 'text': '6\n',
+ 'output_type': 'stream'
+ },
+ {
+ 'name': 'stdout',
+ 'text': '7\n',
+ 'output_type': 'stream'
+ }
+ ]
+ ),
+ new Cell(
+ [
+ 'print(a + 4)'
+ ],
+ 'code',
+ [
+ {
+ 'output_type': 'error',
+ 'ename': 'NameError',
+ 'evalue': 'name \'a\' is not defined',
+ 'traceback': [
+ '\u001b[0;31m---------------------------------------------------------------------------\u001b[0m',
+ '\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)',
+ '\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m4\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m',
+ '\u001b[0;31mNameError\u001b[0m: name \'a\' is not defined'
+ ]
+ }
+ ]
+ )
+ ]
+ );
+ }
+
+ async resolveNotebook(resource: vscode.Uri): Promise {
+ if (this._notebooks.has(resource)) {
+ return this._notebooks.get(resource);
+ }
+
+ this._notebooks.set(resource, this._notebook);
+
+ return Promise.resolve(this._notebook);
+ }
+
+ async executeNotebook(resource: vscode.Uri): Promise {
+ this._notebook.cells.forEach(cell => cell.fillInOutputs());
+
+ this._onDidChangeNotebook.fire({ resource, notebook: this._notebook });
+ return;
+ }
+}
\ No newline at end of file
diff --git a/extensions/notebook-test/src/typings/ref.d.ts b/extensions/notebook-test/src/typings/ref.d.ts
new file mode 100644
index 00000000000..954bab971e3
--- /dev/null
+++ b/extensions/notebook-test/src/typings/ref.d.ts
@@ -0,0 +1,8 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+///
+///
+///
diff --git a/extensions/notebook-test/tsconfig.json b/extensions/notebook-test/tsconfig.json
new file mode 100644
index 00000000000..d0797affbad
--- /dev/null
+++ b/extensions/notebook-test/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../shared.tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out",
+ "experimentalDecorators": true
+ },
+ "include": [
+ "src/**/*"
+ ]
+}
diff --git a/extensions/notebook-test/yarn.lock b/extensions/notebook-test/yarn.lock
new file mode 100644
index 00000000000..e79cafaa0b2
--- /dev/null
+++ b/extensions/notebook-test/yarn.lock
@@ -0,0 +1,46 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+applicationinsights@1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5"
+ integrity sha512-KzOOGdphOS/lXWMFZe5440LUdFbrLpMvh2SaRxn7BmiI550KAoSb2gIhiq6kJZ9Ir3AxRRztjhzif+e5P5IXIg==
+ dependencies:
+ diagnostic-channel "0.2.0"
+ diagnostic-channel-publishers "0.2.1"
+ zone.js "0.7.6"
+
+diagnostic-channel-publishers@0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
+ integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
+
+diagnostic-channel@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
+ integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
+ dependencies:
+ semver "^5.3.0"
+
+semver@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
+ integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==
+
+vscode-extension-telemetry@0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.1.tgz#91387e06b33400c57abd48979b0e790415ae110b"
+ integrity sha512-TkKKG/B/J94DP5qf6xWB4YaqlhWDg6zbbqVx7Bz//stLQNnfE9XS1xm3f6fl24c5+bnEK0/wHgMgZYKIKxPeUA==
+ dependencies:
+ applicationinsights "1.0.8"
+
+vscode-nls@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002"
+ integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw==
+
+zone.js@0.7.6:
+ version "0.7.6"
+ resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
+ integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=
diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts
index 3a1399ee682..92cb6015de9 100644
--- a/src/vs/editor/common/modes.ts
+++ b/src/vs/editor/common/modes.ts
@@ -1447,6 +1447,85 @@ export interface IWebviewPanelOptions {
readonly retainContextWhenHidden?: boolean;
}
+/**
+ * @internal
+ */
+
+export interface INotebookSelectors {
+ readonly filenamePattern?: string;
+}
+
+/**
+ * @internal
+ */
+export interface IStreamOutput {
+ output_type: 'stream';
+ text: string;
+}
+
+/**
+ * @internal
+ */
+export interface IErrorOutput {
+ output_type: 'error';
+ evalue: string;
+ traceback: string[];
+}
+
+/**
+ * @internal
+ */
+export interface IDisplayOutput {
+ output_type: 'display_data';
+ data: { string: string };
+}
+
+/**
+ * @internal
+ */
+export interface IGenericOutput {
+ output_type: string;
+}
+
+/**
+ * @internal
+ */
+export type IOutput = IStreamOutput | any;
+
+/**
+ * @internal
+ */
+export interface ICell {
+ handle: number;
+ source: string[];
+ cell_type: 'markdown' | 'code';
+ outputs: IOutput[];
+ onDidChangeOutputs?: Event;
+}
+
+/**
+ * @internal
+ */
+export interface LanguageInfo {
+ file_extension: string;
+}
+
+/**
+ * @internal
+ */
+export interface IMetadata {
+ language_info: LanguageInfo;
+}
+
+/**
+ * @internal
+ */
+export interface INotebook {
+ handle: number;
+ metadata: IMetadata;
+ cells: ICell[];
+ onDidChangeCells?: Event;
+}
export interface CodeLens {
range: IRange;
diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts
index 19a434638c0..4b21ff75041 100644
--- a/src/vs/vscode.proposed.d.ts
+++ b/src/vs/vscode.proposed.d.ts
@@ -1286,4 +1286,62 @@ declare module 'vscode' {
}
//#endregion
+
+ //#region Peng: Notebook
+
+ export interface IStreamOutput {
+ output_type: 'stream';
+ text: string;
+ }
+
+ export interface IErrorOutput {
+ output_type: 'error';
+ evalue: string;
+ traceback: string[];
+ }
+
+ export interface IDisplayOutput {
+ output_type: 'display_data';
+ data: { string: string };
+ }
+
+ export interface IGenericOutput {
+ output_type: string;
+ }
+
+ export type IOutput = IStreamOutput | any;
+
+ export interface ICell {
+ source: string[];
+ cell_type: 'markdown' | 'code';
+ outputs: IOutput[];
+ }
+
+ export interface LanguageInfo {
+ file_extension: string;
+ }
+
+ export interface IMetadata {
+ language_info: LanguageInfo;
+ }
+
+ export interface INotebook {
+ metadata: IMetadata;
+ cells: ICell[];
+ }
+
+ export interface NotebookProvider {
+ onDidChangeNotebook?: Event<{ resource: Uri, notebook: INotebook }>;
+ resolveNotebook(resource: Uri): Promise;
+ executeNotebook(resource: Uri): Promise;
+ }
+
+ namespace window {
+ export function registerNotebookProvider(
+ notebookType: string,
+ provider: NotebookProvider
+ ): Disposable;
+ }
+
+ //#endregion
}
diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts
index 2905c524113..b536a76ea5d 100644
--- a/src/vs/workbench/api/browser/extensionHost.contribution.ts
+++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts
@@ -54,6 +54,7 @@ import './mainThreadWindow';
import './mainThreadWebview';
import './mainThreadWorkspace';
import './mainThreadComments';
+import './mainThreadNotebook';
import './mainThreadTask';
import './mainThreadLabelService';
import 'vs/workbench/api/common/apiCommands';
diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts
new file mode 100644
index 00000000000..c3172e15762
--- /dev/null
+++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts
@@ -0,0 +1,142 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
+import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext } from '../common/extHost.protocol';
+import { Disposable } from 'vs/base/common/lifecycle';
+import { URI } from 'vs/base/common/uri';
+import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/browser/notebookService';
+import { INotebook, IMetadata, ICell, IOutput } from 'vs/editor/common/modes';
+import { Emitter, Event } from 'vs/base/common/event';
+
+export class MainThreadCell implements ICell {
+ private _onDidChangeOutputs = new Emitter();
+ onDidChangeOutputs: Event = this._onDidChangeOutputs.event;
+
+ private _outputs: IOutput[];
+
+ public get outputs(): IOutput[] {
+ return this._outputs;
+ }
+
+ public set outputs(newOutputs: IOutput[]) {
+ this._outputs = newOutputs;
+ this._onDidChangeOutputs.fire();
+ }
+
+ constructor(
+ public handle: number,
+ public source: string[],
+ public cell_type: 'markdown' | 'code',
+ outputs: IOutput[]
+ ) {
+ this._outputs = outputs;
+ }
+}
+
+export class MainThreadNotebook implements INotebook {
+
+ private readonly _onDidChangeCells = new Emitter();
+ get onDidChangeCells(): Event { return this._onDidChangeCells.event; }
+ private _mapping: Map = new Map();
+ public cells: MainThreadCell[];
+
+ constructor(
+ public handle: number,
+ public metadata: IMetadata,
+ cells: ICell[]
+ ) {
+ this.cells = [];
+ cells.forEach(cell => {
+ let mainCell = new MainThreadCell(cell.handle, cell.source, cell.cell_type, cell.outputs);
+ this._mapping.set(cell.handle, mainCell);
+ this.cells.push(mainCell);
+ });
+ }
+
+ updateCells(newCells: ICell[]) {
+ // todo, handle cell insertion and deletion
+ newCells.forEach(newCell => {
+ let cell = this._mapping.get(newCell.handle);
+ if (cell) {
+ cell.outputs = newCell.outputs;
+ }
+ });
+
+ this._onDidChangeCells.fire();
+ }
+}
+
+@extHostNamedCustomer(MainContext.MainThreadNotebook)
+export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape {
+ private readonly _notebookProviders = new Map();
+ private readonly _proxy: ExtHostNotebookShape;
+
+ constructor(
+ extHostContext: IExtHostContext,
+ @INotebookService private _notebookService: INotebookService
+ ) {
+ super();
+ this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
+ }
+
+ $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): void {
+ let controller = new MainThreadNotebookController(this);
+ this._notebookProviders.set(viewType, controller);
+ this._notebookService.registerNotebookController(viewType, controller);
+ }
+
+ $unregisterNotebookProvider(viewType: string): void {
+ this._notebookProviders.delete(viewType);
+ this._notebookService.unregisterNotebookProvider(viewType);
+ }
+
+ $updateNotebook(viewType: string, uri: URI, notebook: INotebook): void {
+ let controller = this._notebookProviders.get(viewType);
+
+ if (controller) {
+ controller.updateNotebook(uri, notebook);
+ }
+ }
+
+ resolveNotebook(viewType: string, uri: URI): Promise {
+ return this._proxy.$resolveNotebook(viewType, uri);
+ }
+
+ executeNotebook(viewType: string, uri: URI): Promise {
+ return this._proxy.$executeNotebook(viewType, uri);
+ }
+}
+
+export class MainThreadNotebookController implements IMainNotebookController {
+ private _mapping: Map = new Map();
+
+ constructor(
+ private _mainThreadNotebook: MainThreadNotebooks
+ ) {
+ }
+
+ async resolveNotebook(viewType: string, uri: URI): Promise {
+ let notebook = await this._mainThreadNotebook.resolveNotebook(viewType, uri);
+ if (notebook) {
+ let mainthreadNotebook = new MainThreadNotebook(notebook.handle, notebook.metadata, notebook.cells);
+ this._mapping.set(uri.toString(), mainthreadNotebook);
+ return mainthreadNotebook;
+ }
+ return undefined;
+ }
+
+ async executeNotebook(viewType: string, uri: URI): Promise {
+ this._mainThreadNotebook.executeNotebook(viewType, uri);
+ }
+
+ updateNotebook(uri: URI, notebook: INotebook): void {
+ let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
+
+ if (mainthreadNotebook) {
+ mainthreadNotebook.updateCells(notebook.cells);
+ }
+ }
+}
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
index ea5ad7991fa..27b473b210f 100644
--- a/src/vs/workbench/api/common/extHost.api.impl.ts
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts
@@ -67,6 +67,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
+import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
export interface IExtensionApiFactory {
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
@@ -123,6 +124,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol));
+ const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol));
// Check that no named customers are missing
const expected: ProxyIdentifier[] = values(ExtHostContext);
@@ -550,6 +552,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
},
createInputBox(): vscode.InputBox {
return extHostQuickOpen.createInputBox(extension.identifier);
+ },
+ registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => {
+ return extHostNotebook.registerNotebookProvider(extension, viewType, provider);
}
};
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
index 3dab81c9c55..957149605f3 100644
--- a/src/vs/workbench/api/common/extHost.protocol.ts
+++ b/src/vs/workbench/api/common/extHost.protocol.ts
@@ -556,6 +556,11 @@ export interface WebviewExtensionDescription {
readonly location: UriComponents;
}
+export interface NotebookExtensionDescription {
+ readonly id: ExtensionIdentifier;
+ readonly location: UriComponents;
+}
+
export enum WebviewEditorCapabilities {
Editable,
}
@@ -606,6 +611,12 @@ export interface ExtHostWebviewsShape {
$onSaveAs(handle: WebviewPanelHandle, resource: UriComponents, targetResource: UriComponents): Promise;
}
+export interface MainThreadNotebookShape extends IDisposable {
+ $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): void;
+ $unregisterNotebookProvider(viewType: string): void;
+ $updateNotebook(viewType: string, resource: UriComponents, notebook: modes.INotebook): void;
+}
+
export interface MainThreadUrlsShape extends IDisposable {
$registerUriHandler(handle: number, extensionId: ExtensionIdentifier): Promise;
$unregisterUriHandler(handle: number): Promise;
@@ -1387,6 +1398,11 @@ export interface ExtHostCommentsShape {
$toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise;
}
+export interface ExtHostNotebookShape {
+ $resolveNotebook(viewType: string, uri: URI): Promise;
+ $executeNotebook(viewType: string, uri: URI): Promise;
+}
+
export interface ExtHostStorageShape {
$acceptValue(shared: boolean, key: string, value: object | undefined): void;
}
@@ -1431,7 +1447,8 @@ export const MainContext = {
MainThreadSearch: createMainId('MainThreadSearch'),
MainThreadTask: createMainId('MainThreadTask'),
MainThreadWindow: createMainId('MainThreadWindow'),
- MainThreadLabelService: createMainId('MainThreadLabelService')
+ MainThreadLabelService: createMainId('MainThreadLabelService'),
+ MainThreadNotebook: createMainId('MainThreadNotebook')
};
export const ExtHostContext = {
@@ -1465,5 +1482,6 @@ export const ExtHostContext = {
ExtHostStorage: createMainId('ExtHostStorage'),
ExtHostUrls: createExtId('ExtHostUrls'),
ExtHostOutputService: createMainId('ExtHostOutputService'),
- ExtHosLabelService: createMainId('ExtHostLabelService')
+ ExtHosLabelService: createMainId('ExtHostLabelService'),
+ ExtHostNotebook: createMainId('ExtHostNotebook')
};
diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts
new file mode 100644
index 00000000000..2a8cf310204
--- /dev/null
+++ b/src/vs/workbench/api/common/extHostNotebook.ts
@@ -0,0 +1,127 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { ExtHostNotebookShape, IMainContext, MainThreadNotebookShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
+import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
+import { Disposable as VSCodeDisposable } from './extHostTypes';
+import { INotebook } from 'vs/editor/common/modes';
+import { URI } from 'vs/base/common/uri';
+import { DisposableStore } from 'vs/base/common/lifecycle';
+
+export class ExtHostCell implements vscode.ICell {
+ private static _handlePool: number = 0;
+ readonly handle = ExtHostCell._handlePool++;
+
+ source: string[];
+ cell_type: 'markdown' | 'code';
+ outputs: any[];
+
+ constructor(
+ raw_cell: vscode.ICell
+ ) {
+ this.source = raw_cell.source;
+ this.cell_type = raw_cell.cell_type;
+ this.outputs = raw_cell.outputs;
+ }
+}
+
+export class ExtHostNotebook implements vscode.INotebook {
+ private static _handlePool: number = 0;
+ readonly handle = ExtHostNotebook._handlePool++;
+ public cells: ExtHostCell[];
+ public cellMapping: Map = new Map();
+
+ constructor(
+ public metadata: vscode.IMetadata,
+ private raw_cells: vscode.ICell[]
+ ) {
+ this.cells = raw_cells.map(cell => {
+ let extHostCell = new ExtHostCell(cell);
+ this.cellMapping.set(cell, extHostCell);
+ return extHostCell;
+ });
+ }
+
+ updateCells(newCells: vscode.ICell[]) {
+ newCells.forEach(cell => {
+ let extHostCell = this.cellMapping.get(cell);
+
+ if (extHostCell) {
+ extHostCell.outputs = cell.outputs;
+ }
+ });
+
+ // trigger update
+ }
+}
+
+export class ExtHostNotebookController implements ExtHostNotebookShape {
+ private readonly _proxy: MainThreadNotebookShape;
+ private readonly _notebookProviders = new Map();
+ private readonly _notebookCache = new Map();
+ private readonly _localStore = new DisposableStore();
+
+ constructor(mainContext: IMainContext) {
+ this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook);
+ }
+
+ public registerNotebookProvider(
+ extension: IExtensionDescription,
+ viewType: string,
+ provider: vscode.NotebookProvider,
+ ): vscode.Disposable {
+
+ if (this._notebookProviders.has(viewType)) {
+ throw new Error(`Notebook provider for '${viewType}' already registered`);
+ }
+
+ this._notebookProviders.set(viewType, { extension, provider });
+ this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType);
+
+ if (provider.onDidChangeNotebook) {
+ this._localStore.add(provider.onDidChangeNotebook!((e) => {
+ let resource = e.resource;
+ let notebook = e.notebook;
+
+ let cachedNotebook = this._notebookCache.get(resource.toString());
+
+ if (cachedNotebook) {
+ cachedNotebook.updateCells(notebook.cells);
+
+ this._proxy.$updateNotebook(viewType, resource, cachedNotebook);
+ }
+ }));
+ }
+
+ return new VSCodeDisposable(() => {
+ this._notebookProviders.delete(viewType);
+ this._proxy.$unregisterNotebookProvider(viewType);
+ });
+ }
+
+ async $resolveNotebook(viewType: string, uri: URI): Promise {
+ let provider = this._notebookProviders.get(viewType);
+
+ if (provider) {
+ let notebook = await provider.provider.resolveNotebook(uri);
+ if (notebook) {
+ let extHostNotebook = new ExtHostNotebook(notebook.metadata, notebook.cells);
+ this._notebookCache.set(uri.toString(), extHostNotebook);
+ return extHostNotebook;
+ }
+ }
+
+ return Promise.resolve(undefined);
+ }
+
+ async $executeNotebook(viewType: string, uri: URI): Promise {
+ let provider = this._notebookProviders.get(viewType);
+
+ if (provider) {
+ return provider.provider.executeNotebook(uri);
+ }
+ }
+}
diff --git a/src/vs/workbench/contrib/notebook/browser/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/cellRenderer.ts
index a16cbdbdfc4..dd396ca3b6d 100644
--- a/src/vs/workbench/contrib/notebook/browser/cellRenderer.ts
+++ b/src/vs/workbench/contrib/notebook/browser/cellRenderer.ts
@@ -6,7 +6,6 @@
import 'vs/css!./notebook';
import * as DOM from 'vs/base/browser/dom';
import { IThemeService } from 'vs/platform/theme/common/themeService';
-import { ICell } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
@@ -34,6 +33,7 @@ import { Emitter } from 'vs/base/common/event';
import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview';
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import * as UUID from 'vs/base/common/uuid';
+import { ICell } from 'vs/editor/common/modes';
export class ViewCell {
private _textModel: ITextModel | null = null;
@@ -47,6 +47,9 @@ export class ViewCell {
protected readonly _onDidChangeEditingState = new Emitter();
readonly onDidChangeEditingState = this._onDidChangeEditingState.event;
+ protected readonly _onDidChangeOutputs = new Emitter();
+ readonly onDidChangeOutputs = this._onDidChangeOutputs.event;
+
get cellType() {
return this.cell.cell_type;
}
@@ -77,6 +80,11 @@ export class ViewCell {
private readonly modeService: IModeService
) {
this.id = UUID.generateUuid();
+ if (this.cell.onDidChangeOutputs) {
+ this.cell.onDidChangeOutputs(() => {
+ this._onDidChangeOutputs.fire();
+ });
+ }
}
hasDynamicHeight() {
@@ -563,7 +571,54 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
this.showContextMenu(element, e.posx, top + height);
});
- this.disposables.set(templateData.cellContainer, listener);
+ let rerenderOutput = element.onDidChangeOutputs(() => {
+ if (element.outputs.length > 0) {
+ let hasDynamicHeight = true;
+ for (let i = 0; i < element.outputs.length; i++) {
+ let result = MimeTypeRenderer.render(element.outputs[i], this.themeService, this.webviewService);
+ if (result) {
+ hasDynamicHeight = hasDynamicHeight || result?.hasDynamicHeight;
+ templateData.outputContainer?.appendChild(result.element);
+ if (result.shadowContent) {
+ this.handler.createContentWidget(element, result.shadowContent, totalHeight + 8);
+ }
+ }
+ }
+
+ if (height !== undefined && hasDynamicHeight) {
+ let dimensions = DOM.getClientArea(templateData.outputContainer!);
+ const elementSizeObserver = new ElementSizeObserver(templateData.outputContainer!, dimensions, () => {
+ if (templateData.outputContainer && document.body.contains(templateData.outputContainer!)) {
+ let height = elementSizeObserver.getHeight();
+ if (dimensions.height !== height) {
+ element.setDynamicHeight(totalHeight + 32 + height);
+ this.handler.layoutElement(element, totalHeight + 32 + height);
+ }
+
+ elementSizeObserver.dispose();
+ }
+ });
+ elementSizeObserver.startObserving();
+ if (!hasDynamicHeight && dimensions.height !== 0) {
+ element.setDynamicHeight(totalHeight + 32 + dimensions.height);
+ this.handler.layoutElement(element, totalHeight + 32 + dimensions.height);
+ }
+
+ this.disposables.set(templateData.cellContainer, {
+ dispose: () => {
+ elementSizeObserver.dispose();
+ }
+ });
+ }
+ }
+ });
+
+ this.disposables.set(templateData.cellContainer, {
+ dispose: () => {
+ listener.dispose();
+ rerenderOutput.dispose();
+ }
+ });
if (templateData.outputContainer) {
templateData.outputContainer!.innerHTML = '';
diff --git a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts
new file mode 100644
index 00000000000..57237791ecc
--- /dev/null
+++ b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts
@@ -0,0 +1,66 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IJSONSchema } from 'vs/base/common/jsonSchema';
+import * as nls from 'vs/nls';
+import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
+import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService';
+import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookProvider';
+
+namespace NotebookEditorContribution {
+ export const viewType = 'viewType';
+ export const displayName = 'displayName';
+ export const selector = 'selector';
+}
+
+
+interface INotebookEditorContribution {
+ readonly [NotebookEditorContribution.viewType]: string;
+ readonly [NotebookEditorContribution.displayName]: string;
+ readonly [NotebookEditorContribution.selector]?: readonly NotebookSelector[];
+}
+
+const notebookContribution: IJSONSchema = {
+ description: nls.localize('contributes.notebook', 'Contributes notebook.'),
+ type: 'array',
+ defaultSnippets: [{ body: [{ viewType: '', displayName: '' }] }],
+ items: {
+ type: 'object',
+ required: [
+ NotebookEditorContribution.viewType,
+ NotebookEditorContribution.displayName,
+ NotebookEditorContribution.selector,
+ ],
+ properties: {
+ [NotebookEditorContribution.viewType]: {
+ type: 'string',
+ description: nls.localize('contributes.notebook.viewType', 'Unique identifier of the notebook.'),
+ },
+ [NotebookEditorContribution.displayName]: {
+ type: 'string',
+ description: nls.localize('contributes.notebook.displayName', 'Human readable name of the notebook.'),
+ },
+ [NotebookEditorContribution.selector]: {
+ type: 'array',
+ description: nls.localize('contributes.notebook.selector', 'Set of globs that the notebook is for.'),
+ items: {
+ type: 'object',
+ properties: {
+ filenamePattern: {
+ type: 'string',
+ description: nls.localize('contributes.notebook.selector.filenamePattern', 'Glob that the notebook is enabled for.'),
+ },
+ }
+ }
+ }
+ }
+ }
+};
+
+export const notebookExtensionPoint = ExtensionsRegistry.registerExtensionPoint({
+ extensionPoint: 'notebookProvider',
+ deps: [languagesExtPoint],
+ jsonSchema: notebookContribution
+});
diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
index 720613fe8ba..40ff2cfa622 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
@@ -16,6 +16,12 @@ import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/ed
import { endsWith } from 'vs/base/common/strings';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
+import { INotebookService, NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService';
+import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
+import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions';
+import { IActiveCodeEditor, isDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
+import { Action } from 'vs/base/common/actions';
+import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
Registry.as(EditorExtensions.Editors).registerEditor(
EditorDescriptor.create(
@@ -28,39 +34,112 @@ Registry.as(EditorExtensions.Editors).registerEditor(
]
);
+
+export class ExecuteNotebookAction extends Action {
+
+ static readonly ID = 'workbench.action.executeNotebook';
+ static readonly LABEL = 'Execute Notebook';
+
+ constructor(
+ id: string,
+ label: string,
+ @IEditorService private readonly editorService: IEditorService,
+ @INotebookService private readonly notebookService: INotebookService
+
+ ) {
+ super(id, label);
+ }
+
+ async run(): Promise {
+ let resource = this.editorService.activeEditor?.getResource();
+
+ if (resource) {
+ let notebookProviders = this.notebookService.getContributedNotebook(resource!);
+
+ if (notebookProviders.length > 0) {
+ let viewType = notebookProviders[0].id;
+ this.notebookService.executeNotebook(viewType, resource);
+ }
+ }
+ }
+}
+
export class NotebookContribution implements IWorkbenchContribution {
private _resourceMapping: Map = new Map();
constructor(
@IEditorService private readonly editorService: IEditorService,
+ @INotebookService private readonly notebookService: INotebookService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
this.editorService.overrideOpenEditor((editor, options, group) => this.onEditorOpening(editor, options, group));
+
+ this.registerCommands();
}
private onEditorOpening(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined {
const resource = editor.getResource();
+ let viewType: string | undefined = undefined;
- if (
- !resource ||
- !endsWith(resource.path, '.ipynb')
- ) {
- return undefined;
+ if (resource) {
+ let notebookProviders = this.notebookService.getContributedNotebook(resource!);
+
+ if (notebookProviders.length > 0) {
+ viewType = notebookProviders[0].id;
+ }
}
- if (this._resourceMapping.has(resource.path)) {
- const input = this._resourceMapping.get(resource.path);
+ if (viewType === undefined) {
+ if (
+ !resource ||
+ !endsWith(resource.path, '.ipynb')
+ ) {
+ return undefined;
+ }
+ }
+
+ if (this._resourceMapping.has(resource!.path)) {
+ const input = this._resourceMapping.get(resource!.path);
return { override: this.editorService.openEditor(input!, options, group) };
}
- const input = this.instantiationService.createInstance(NotebookEditorInput, editor);
- this._resourceMapping.set(resource.path, input);
+
+ const input = this.instantiationService.createInstance(NotebookEditorInput, editor, viewType);
+ this._resourceMapping.set(resource!.path, input);
return { override: this.editorService.openEditor(input, { ...options, ignoreOverrides: true }, group) };
}
+
+ private registerCommands() {
+ const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions);
+
+ workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ExecuteNotebookAction, ExecuteNotebookAction.ID, ExecuteNotebookAction.LABEL), 'Execute Notebook', 'Notebook');
+ }
}
const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(NotebookContribution, LifecyclePhase.Starting);
+
+
+registerSingleton(INotebookService, NotebookService);
+
+
+export function getActiveEditor(editorService: IEditorService): IActiveCodeEditor | null {
+ let activeTextEditorWidget = editorService.activeTextEditorWidget;
+
+ if (isDiffEditor(activeTextEditorWidget)) {
+ if (activeTextEditorWidget.getOriginalEditor().hasTextFocus()) {
+ activeTextEditorWidget = activeTextEditorWidget.getOriginalEditor();
+ } else {
+ activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor();
+ }
+ }
+
+ if (!isCodeEditor(activeTextEditorWidget) || !activeTextEditorWidget.hasModel()) {
+ return null;
+ }
+
+ return activeTextEditorWidget;
+}
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
index 25227033cda..d91ba5ff578 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
@@ -191,6 +191,10 @@ export class NotebookEditor extends BaseEditor implements NotebookHandler {
}
this.model = model;
+ this.localStore.add(this.model.onDidChangeCells(() => {
+ this.updateViewCells();
+ }));
+
this.viewCells = model.getNotebook().cells.map(cell => {
return new ViewCell(cell, false, this.modelService, this.modeService);
});
@@ -243,8 +247,13 @@ export class NotebookEditor extends BaseEditor implements NotebookHandler {
}, 0);
}
+ updateViewCells() {
+ this.list?.rerender();
+ }
+
insertEmptyNotebookCell(cell: ViewCell, type: 'code' | 'markdown', direction: 'above' | 'below') {
let newCell = new ViewCell({
+ handle: -1,
cell_type: type,
source: [],
outputs: []
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts
index fcc278a19e2..3747ea0cc41 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts
@@ -9,59 +9,30 @@ import { ITextModel } from 'vs/editor/common/model';
import { format } from 'vs/base/common/jsonFormatter';
import { applyEdits } from 'vs/base/common/jsonEdit';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
-import { Emitter } from 'vs/base/common/event';
-
-export interface IStreamOutput {
- output_type: 'stream';
- text: string;
-}
-
-export interface IErrorOutput {
- output_type: 'error';
- evalue: string;
- traceback: string[];
-}
-
-export interface IDisplayOutput {
- output_type: 'display_data';
- data: { string: string };
-}
-
-export interface IGenericOutput {
- output_type: string;
-}
-
-export type IOutput = IStreamOutput | any;
-
-export interface ICell {
- source: string[];
- cell_type: 'markdown' | 'code';
- outputs: IOutput[];
-}
-
-export interface LanguageInfo {
- file_extension: string;
-}
-export interface IMetadata {
- language_info: LanguageInfo;
-}
-export interface INotebook {
- metadata: IMetadata;
- cells: ICell[];
-}
-
+import { Emitter, Event } from 'vs/base/common/event';
+import { INotebook, ICell } from 'vs/editor/common/modes';
+import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService';
export class NotebookEditorModel extends EditorModel {
- private _notebook: INotebook | undefined;
private _dirty = false;
protected readonly _onDidChangeDirty = this._register(new Emitter());
readonly onDidChangeDirty = this._onDidChangeDirty.event;
+ private readonly _onDidChangeCells = new Emitter();
+ get onDidChangeCells(): Event { return this._onDidChangeCells.event; }
+
constructor(
- public readonly textModel: ITextModel
+ public readonly textModel: ITextModel,
+ private _notebook: INotebook | undefined
) {
super();
+
+ if (_notebook && _notebook.onDidChangeCells) {
+ this._register(_notebook.onDidChangeCells(() => {
+ this._onDidChangeCells.fire();
+ }));
+ }
}
isDirty() {
@@ -115,6 +86,8 @@ export class NotebookEditorInput extends EditorInput {
constructor(
public readonly editorInput: IEditorInput,
+ public readonly viewType: string | undefined,
+ @INotebookService private readonly notebookService: INotebookService,
@ITextFileService private readonly textFileService: ITextFileService,
@ITextModelService private readonly textModelResolverService: ITextModelService
) {
@@ -142,13 +115,22 @@ export class NotebookEditorInput extends EditorInput {
return Promise.resolve(true);
}
+ getResource() {
+ return this.editorInput.getResource();
+ }
+
resolve(): Promise {
if (!this.promise) {
this.promise = this.textModelResolverService.createModelReference(this.editorInput.getResource()!)
- .then(ref => {
+ .then(async ref => {
const textModel = ref.object.textEditorModel;
- this.textModel = new NotebookEditorModel(textModel);
+ let notebook: INotebook | undefined = undefined;
+ if (this.viewType !== undefined) {
+ notebook = await this.notebookService.resolveNotebook(this.viewType, this.editorInput.getResource()!);
+ }
+
+ this.textModel = new NotebookEditorModel(textModel, notebook);
this.textModel.onDidChangeDirty(() => this._onDidChangeDirty.fire());
return this.textModel;
});
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookService.ts b/src/vs/workbench/contrib/notebook/browser/notebookService.ts
new file mode 100644
index 00000000000..fd53a249c99
--- /dev/null
+++ b/src/vs/workbench/contrib/notebook/browser/notebookService.ts
@@ -0,0 +1,112 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Disposable } from 'vs/base/common/lifecycle';
+import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
+import { INotebook } from 'vs/editor/common/modes';
+import { URI } from 'vs/base/common/uri';
+import { notebookExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint';
+import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
+
+export const INotebookService = createDecorator('notebookService');
+
+export interface IMainNotebookController {
+ resolveNotebook(viewType: string, uri: URI): Promise;
+ executeNotebook(viewType: string, uri: URI): Promise;
+ updateNotebook(uri: URI, notebook: INotebook): void;
+}
+
+export interface INotebookService {
+ _serviceBrand: undefined;
+ registerNotebookController(viewType: string, controller: IMainNotebookController): void;
+ unregisterNotebookProvider(viewType: string): void;
+ resolveNotebook(viewType: string, uri: URI): Promise;
+ executeNotebook(viewType: string, uri: URI): Promise;
+ getContributedNotebook(resource: URI): readonly NotebookProviderInfo[];
+}
+
+export class NotebookInfoStore {
+ private readonly contributedEditors = new Map();
+
+ public clear() {
+ this.contributedEditors.clear();
+ }
+
+ public get(viewType: string): NotebookProviderInfo | undefined {
+ return this.contributedEditors.get(viewType);
+ }
+
+ public add(info: NotebookProviderInfo): void {
+ if (this.contributedEditors.has(info.id)) {
+ console.log(`Custom editor with id '${info.id}' already registered`);
+ return;
+ }
+ this.contributedEditors.set(info.id, info);
+ }
+
+ public getContributedNotebook(resource: URI): readonly NotebookProviderInfo[] {
+ return Array.from(this.contributedEditors.values()).filter(customEditor =>
+ customEditor.matches(resource));
+ }
+}
+
+
+export class NotebookService extends Disposable implements INotebookService {
+ _serviceBrand: undefined;
+ private readonly _notebookProviders = new Map();
+ public notebookProviderInfoStore: NotebookInfoStore = new NotebookInfoStore();
+
+ constructor() {
+ super();
+
+ notebookExtensionPoint.setHandler((extensions) => {
+ this.notebookProviderInfoStore.clear();
+
+ for (const extension of extensions) {
+ for (const notebookContribution of extension.value) {
+ this.notebookProviderInfoStore.add(new NotebookProviderInfo({
+ id: notebookContribution.viewType,
+ displayName: notebookContribution.displayName,
+ selector: notebookContribution.selector || [],
+ }));
+ }
+ }
+ // console.log(this._notebookProviderInfoStore);
+ });
+
+ }
+
+ registerNotebookController(viewType: string, controller: IMainNotebookController) {
+ this._notebookProviders.set(viewType, controller);
+ }
+
+ unregisterNotebookProvider(viewType: string): void {
+ this._notebookProviders.delete(viewType);
+ }
+
+ resolveNotebook(viewType: string, uri: URI): Promise {
+ let provider = this._notebookProviders.get(viewType);
+
+ if (provider) {
+ return provider.resolveNotebook(viewType, uri);
+ }
+
+ return Promise.resolve(undefined);
+ }
+
+ async executeNotebook(viewType: string, uri: URI): Promise {
+ let provider = this._notebookProviders.get(viewType);
+
+ if (provider) {
+ return provider.executeNotebook(viewType, uri);
+ }
+
+ return;
+ }
+
+ public getContributedNotebook(resource: URI): readonly NotebookProviderInfo[] {
+ return this.notebookProviderInfoStore.getContributedNotebook(resource);
+ }
+}
diff --git a/src/vs/workbench/contrib/notebook/browser/outputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/outputRenderer.ts
index f2cbd9ad0a7..165a9fb25b0 100644
--- a/src/vs/workbench/contrib/notebook/browser/outputRenderer.ts
+++ b/src/vs/workbench/contrib/notebook/browser/outputRenderer.ts
@@ -7,10 +7,10 @@ import * as DOM from 'vs/base/browser/dom';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { RGBA, Color } from 'vs/base/common/color';
import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
-import { IOutput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview';
import { isArray } from 'vs/base/common/types';
import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/cellRenderer';
+import { IOutput } from 'vs/editor/common/modes';
export function registerMineTypeRenderer(types: string[], renderer: IMimeRenderer) {
types.forEach(type => {
diff --git a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts
new file mode 100644
index 00000000000..3a2f2bf15ee
--- /dev/null
+++ b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts
@@ -0,0 +1,42 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as glob from 'vs/base/common/glob';
+import { URI } from 'vs/base/common/uri';
+import { basename } from 'vs/base/common/resources';
+
+export interface NotebookSelector {
+ readonly filenamePattern?: string;
+}
+
+export class NotebookProviderInfo {
+
+ public readonly id: string;
+ public readonly displayName: string;
+ public readonly selector: readonly NotebookSelector[];
+
+ constructor(descriptor: {
+ readonly id: string;
+ readonly displayName: string;
+ readonly selector: readonly NotebookSelector[];
+ }) {
+ this.id = descriptor.id;
+ this.displayName = descriptor.displayName;
+ this.selector = descriptor.selector;
+ }
+
+ matches(resource: URI): boolean {
+ return this.selector.some(selector => NotebookProviderInfo.selectorMatches(selector, resource));
+ }
+
+ static selectorMatches(selector: NotebookSelector, resource: URI): boolean {
+ if (selector.filenamePattern) {
+ if (glob.match(selector.filenamePattern.toLowerCase(), basename(resource).toLowerCase())) {
+ return true;
+ }
+ }
+ return false;
+ }
+}