Test extension for notebook

This commit is contained in:
rebornix
2020-01-06 15:26:12 -08:00
parent 6f61266044
commit 3be5087f13
26 changed files with 1173 additions and 59 deletions

View File

@@ -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"
]
}
]
}

View File

@@ -0,0 +1 @@
# Notebook test

View File

@@ -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',
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.6, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#C5C5C5;}
</style>
<g>
<path class="st0" d="M2,14.1l-1.1-1L0.9,3l1-1.1l12.1,0l1.1,1l0,10.1L14,14.1H2z M13.9,12.9V3.1H2.1v9.8H13.9z M8.9,12.1V7.9h4.2
v4.2H8.9z M11.9,10.9V9.1h-1.8v1.8H11.9z M2.9,12.1v-1.2h4.2v1.2H2.9z M2.9,9.1V7.9h4.2v1.2H2.9z M2.9,7.1V3.9h10.2v3.2H2.9z
M11.9,5.9V5.1H4.1v0.8H11.9z"/>
<path d="M14,2l1,1v10l-1,1H2l-1-1V3l1-1H14 M2,13h12V3H2V13 M13,4v3H3V4H13 M4,6h8V5H4V6 M13,8v4H9V8H13 M10,11h2V9h-2V11 M7,8v1H3
V8H7 M7,11v1H3v-1H7 M14.1,1.8L14.1,1.8H2H1.9L1.9,1.9l-1,1L0.8,2.9V3v10v0.1l0.1,0.1l1,1l0.1,0.1H2h12h0.1l0.1-0.1l1-1l0.1-0.1V13
V3V2.9l-0.1-0.1L14.1,1.8L14.1,1.8L14.1,1.8z M2.2,3.2h11.6v9.6H2.2V3.2L2.2,3.2z M13.2,3.8H13H3H2.8V4v3v0.2H3h10h0.2V7V4V3.8
L13.2,3.8z M4.2,5.2h7.6v0.6H4.2V5.2L4.2,5.2z M13.2,7.8H13H9H8.8V8v4v0.2H9h4h0.2V12V8V7.8L13.2,7.8z M10.2,9.2h1.6v1.6h-1.6V9.2
L10.2,9.2z M7.2,7.8H7H3H2.8V8v1v0.2H3h4h0.2V9V8V7.8L7.2,7.8z M7.2,10.8H7H3H2.8V11v1v0.2H3h4h0.2V12v-1V10.8L7.2,10.8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -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"
}
}

View File

@@ -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()));
}

View File

@@ -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<vscode.Uri, JupyterNotebook> = 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<ipython-input-1-f270cadddfe4>\u001b[0m in \u001b[0;36m<module>\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<vscode.INotebook | undefined> {
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<void> {
this._notebook.cells.forEach(cell => cell.fillInOutputs());
this._onDidChangeNotebook.fire({ resource, notebook: this._notebook });
return;
}
}

View File

@@ -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.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>
/// <reference types='@types/node'/>

View File

@@ -0,0 +1,10 @@
{
"extends": "../shared.tsconfig.json",
"compilerOptions": {
"outDir": "./out",
"experimentalDecorators": true
},
"include": [
"src/**/*"
]
}

View File

@@ -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=

View File

@@ -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<void>;
}
/**
* @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<void>;
}
export interface CodeLens {
range: IRange;

View File

@@ -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<INotebook | undefined>;
executeNotebook(resource: Uri): Promise<void>;
}
namespace window {
export function registerNotebookProvider(
notebookType: string,
provider: NotebookProvider
): Disposable;
}
//#endregion
}

View File

@@ -54,6 +54,7 @@ import './mainThreadWindow';
import './mainThreadWebview';
import './mainThreadWorkspace';
import './mainThreadComments';
import './mainThreadNotebook';
import './mainThreadTask';
import './mainThreadLabelService';
import 'vs/workbench/api/common/apiCommands';

View File

@@ -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<void>();
onDidChangeOutputs: Event<void> = 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<void>();
get onDidChangeCells(): Event<void> { return this._onDidChangeCells.event; }
private _mapping: Map<number, MainThreadCell> = 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<string, MainThreadNotebookController>();
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<INotebook | undefined> {
return this._proxy.$resolveNotebook(viewType, uri);
}
executeNotebook(viewType: string, uri: URI): Promise<void> {
return this._proxy.$executeNotebook(viewType, uri);
}
}
export class MainThreadNotebookController implements IMainNotebookController {
private _mapping: Map<string, MainThreadNotebook> = new Map();
constructor(
private _mainThreadNotebook: MainThreadNotebooks
) {
}
async resolveNotebook(viewType: string, uri: URI): Promise<INotebook | undefined> {
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<void> {
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);
}
}
}

View File

@@ -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<any>[] = 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);
}
};

View File

@@ -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<void>;
}
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<void>;
$unregisterUriHandler(handle: number): Promise<void>;
@@ -1387,6 +1398,11 @@ export interface ExtHostCommentsShape {
$toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise<void>;
}
export interface ExtHostNotebookShape {
$resolveNotebook(viewType: string, uri: URI): Promise<modes.INotebook | undefined>;
$executeNotebook(viewType: string, uri: URI): Promise<void>;
}
export interface ExtHostStorageShape {
$acceptValue(shared: boolean, key: string, value: object | undefined): void;
}
@@ -1431,7 +1447,8 @@ export const MainContext = {
MainThreadSearch: createMainId<MainThreadSearchShape>('MainThreadSearch'),
MainThreadTask: createMainId<MainThreadTaskShape>('MainThreadTask'),
MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'),
MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService')
MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService'),
MainThreadNotebook: createMainId<MainThreadNotebookShape>('MainThreadNotebook')
};
export const ExtHostContext = {
@@ -1465,5 +1482,6 @@ export const ExtHostContext = {
ExtHostStorage: createMainId<ExtHostStorageShape>('ExtHostStorage'),
ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls'),
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService')
ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
ExtHostNotebook: createMainId<ExtHostNotebookShape>('ExtHostNotebook')
};

View File

@@ -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<vscode.ICell, ExtHostCell> = 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<string, { readonly provider: vscode.NotebookProvider, readonly extension: IExtensionDescription }>();
private readonly _notebookCache = new Map<string, ExtHostNotebook>();
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<INotebook | undefined> {
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<void> {
let provider = this._notebookProviders.get(viewType);
if (provider) {
return provider.provider.executeNotebook(uri);
}
}
}

View File

@@ -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<void>();
readonly onDidChangeEditingState = this._onDidChangeEditingState.event;
protected readonly _onDidChangeOutputs = new Emitter<void>();
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 = '';

View File

@@ -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<INotebookEditorContribution[]>({
extensionPoint: 'notebookProvider',
deps: [languagesExtPoint],
jsonSchema: notebookContribution
});

View File

@@ -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<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
EditorDescriptor.create(
@@ -28,39 +34,112 @@ Registry.as<IEditorRegistry>(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<void> {
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<string, NotebookEditorInput> = new Map<string, NotebookEditorInput>();
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<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ExecuteNotebookAction, ExecuteNotebookAction.ID, ExecuteNotebookAction.LABEL), 'Execute Notebook', 'Notebook');
}
}
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(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;
}

View File

@@ -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: []

View File

@@ -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<void>());
readonly onDidChangeDirty = this._onDidChangeDirty.event;
private readonly _onDidChangeCells = new Emitter<void>();
get onDidChangeCells(): Event<void> { 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<NotebookEditorModel> {
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;
});

View File

@@ -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<INotebookService>('notebookService');
export interface IMainNotebookController {
resolveNotebook(viewType: string, uri: URI): Promise<INotebook | undefined>;
executeNotebook(viewType: string, uri: URI): Promise<void>;
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<INotebook | undefined>;
executeNotebook(viewType: string, uri: URI): Promise<void>;
getContributedNotebook(resource: URI): readonly NotebookProviderInfo[];
}
export class NotebookInfoStore {
private readonly contributedEditors = new Map<string, NotebookProviderInfo>();
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<string, IMainNotebookController>();
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<INotebook | undefined> {
let provider = this._notebookProviders.get(viewType);
if (provider) {
return provider.resolveNotebook(viewType, uri);
}
return Promise.resolve(undefined);
}
async executeNotebook(viewType: string, uri: URI): Promise<void> {
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);
}
}

View File

@@ -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 => {

View File

@@ -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;
}
}