Add notebook serializer sample

Ported from the microsoft/notebook-extension-samples repo
This commit is contained in:
Rob Lourens
2022-12-30 11:51:28 -08:00
parent 61d94d731c
commit 739e41ab50
22 changed files with 7482 additions and 0 deletions

View File

@ -0,0 +1,23 @@
/**@type {import('eslint').Linter.Config} */
// eslint-disable-next-line no-undef
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
rules: {
'semi': [
2,
"always"
],
'@typescript-eslint/no-unused-vars': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-non-null-assertion': 0,
}
};

View File

@ -0,0 +1,5 @@
{
"recommendations": [
"dbaeumer.vscode-eslint"
]
}

36
notebook-serializer/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,36 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// 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",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"debugWebviews": true,
"trace": true,
"preLaunchTask": "npm: watch"
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
],
"outFiles": [
"${workspaceFolder}/out/test/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
}
]
}

View File

@ -0,0 +1,6 @@
{
"search.exclude": {
"out": true
},
"typescript.tsc.autoDetect": "off"
}

26
notebook-serializer/.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,26 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": ["$tsc-watch", "$ts-checker-webpack-watch"],
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "npm",
"script": "compile",
"problemMatcher": ["$tsc", "$ts-checker-webpack"],
"group": "build"
}
]
}

View File

@ -0,0 +1,12 @@
.vscode/**
.vscode-test/**
out/test/**
src/**
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/tsconfig.json
**/.eslintrc.json
**/*.map
**/*.ts
**/*.tsbuildinfo

View File

@ -0,0 +1,9 @@
# Change Log
All notable changes to the "notebook-serializer-example" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [Unreleased]
- Initial release

View File

@ -0,0 +1,28 @@
# notebook-serializer-example
⚠️ Work-in-progress starter code for custom notebook renderers in VS Code. Expect this to change as notebooks matures. ⚠️
This starter includes:
- 🖥️ TypeScript code to create a simple `NotebookOutputRenderer`
- 📦 A Webpack build for renderer client code
- ⚡ Support for hot module reloading and safe boilerplate
- 🎨 CSS modules support
### Running this Sample
1. `cd notebook-serializer-example`
1. `code-insiders .`: Open the folder in VS Code Insiders
1. Hit `F5` to build+debug
### Structure
A Notebook Renderer consists of code that runs in the VS Code Extension Host (Node.js), which registers the renderer and passes data into the UI code running inside a WebView (Browser/DOM).
This uses TypeScript project references. There are three projects in the `src` directory:
- `extension` contains the code running in Node.js extension host. It's compiled with `tsc`.
- `client` is the UI code, built by Webpack, with access to the DOM.
- `common` contains code shared between the extension and client.
When you run `watch`, `compile`, or `dev`, we invoke both `tsc` and `webpack` to compile the extension and the client portion of the code.

6929
notebook-serializer/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,71 @@
{
"name": "notebook-serializer-example",
"displayName": "notebook-serializer-example",
"description": "Notebook using Serializer API example",
"version": "0.0.1",
"engines": {
"vscode": "^1.74.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onNotebook:test-notebook-renderer"
],
"main": "./out/extension/extension.js",
"contributes": {
"notebookRenderer": [
{
"id": "notebook-serializer-example",
"entrypoint": "./out/client/index.js",
"displayName": "notebook-serializer-example",
"mimeTypes": [
"x-application/sample-json-renderer"
]
}
],
"notebooks": [
{
"type": "test-notebook-renderer",
"displayName": "Sample Notebook",
"selector": [
{
"filenamePattern": "*.sample-json-notebook"
}
]
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile && node out/test/checkNoTestProvider.js",
"compile": "npm run compile:extension && npm run compile:client",
"compile:extension": "tsc -b",
"compile:client": "webpack --mode production",
"lint": "eslint src --ext ts",
"watch": "concurrently -r \"npm:watch:*\"",
"watch:extension": "tsc -b --watch",
"watch:client": "webpack --mode development --watch"
},
"devDependencies": {
"@types/glob": "^7.1.3",
"@types/mocha": "^8.2.2",
"@types/node": "14.x",
"@types/vscode": "^1.74.0",
"@types/vscode-notebook-renderer": "^1.72.0",
"@types/webpack-env": "^1.16.0",
"@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0",
"concurrently": "^5.3.0",
"css-loader": "^4.2.0",
"eslint": "^7.27.0",
"fork-ts-checker-webpack-plugin": "^5.0.14",
"glob": "^7.1.7",
"mocha": "^10.2.0",
"style-loader": "^1.2.1",
"ts-loader": "^9.2.2",
"typescript": "^4.3.2",
"vscode-notebook-error-overlay": "^1.0.1",
"webpack": "^5.38.1",
"webpack-cli": "^4.7.0"
}
}

View File

@ -0,0 +1,4 @@
declare module '*.css' {
const classes: { [className: string]: string };
export = classes;
}

View File

@ -0,0 +1,42 @@
import { render } from './render';
import errorOverlay from 'vscode-notebook-error-overlay';
import type { ActivationFunction } from 'vscode-notebook-renderer';
// Fix the public path so that any async import()'s work as expected.
declare const __webpack_relative_entrypoint_to_root__: string;
declare const scriptUrl: string;
__webpack_public_path__ = new URL(scriptUrl.replace(/[^/]+$/, '') + __webpack_relative_entrypoint_to_root__).toString();
// ----------------------------------------------------------------------------
// This is the entrypoint to the notebook renderer's webview client-side code.
// This contains some boilerplate that calls the `render()` function when new
// output is available. You probably don't need to change this code; put your
// rendering logic inside of the `render()` function.
// ----------------------------------------------------------------------------
export const activate: ActivationFunction = context => {
return {
renderOutputItem(outputItem, element) {
let shadow = element.shadowRoot;
if (!shadow) {
shadow = element.attachShadow({ mode: 'open' });
const root = document.createElement('div');
root.id = 'root';
shadow.append(root);
}
const root = shadow.querySelector<HTMLElement>('#root')!;
errorOverlay.wrap(root, () => {
root.innerHTML = '';
const node = document.createElement('div');
root.appendChild(node);
render({ container: node, mime: outputItem.mime, value: outputItem.json(), context });
});
},
disposeOutputItem(outputId) {
// Do any teardown here. outputId is the cell output being deleted, or
// undefined if we're clearing all outputs.
}
};
};

View File

@ -0,0 +1,32 @@
// We've set up this sample using CSS modules, which lets you import class
// names into JavaScript: https://github.com/css-modules/css-modules
// You can configure or change this in the webpack.config.js file.
import * as style from './style.css';
import type { RendererContext } from 'vscode-notebook-renderer';
interface IRenderInfo {
container: HTMLElement;
mime: string;
value: any;
context: RendererContext<unknown>;
}
// This function is called to render your contents.
export function render({ container, mime, value }: IRenderInfo) {
// Format the JSON and insert it as <pre><code>{ ... }</code></pre>
// Replace this with your custom code!
const pre = document.createElement('pre');
pre.classList.add(style.json);
const code = document.createElement('code');
code.className = 'output';
code.textContent = `mime type: ${mime}\n\n${JSON.stringify(value, null, 2)}`;
pre.appendChild(code);
container.appendChild(pre);
}
if (module.hot) {
module.hot.addDisposeHandler(() => {
// In development, this will be called before the renderer is reloaded. You
// can use this to clean up or stash any state.
});
}

View File

@ -0,0 +1,8 @@
.json code {
font-family: monospace;
font-size: 12px;
}
.output {
background-color: lightblue;
}

View File

@ -0,0 +1,11 @@
{
"extends": "../tsconfig-base.json",
"compilerOptions": {
// noEmit prevents the default tsc from building this--we use webpack instead
"noEmit": true,
"rootDir": ".",
"module": "esnext",
"lib": ["ES2019", "dom"],
"types": ["webpack-env", "vscode-notebook-renderer"],
}
}

View File

@ -0,0 +1,52 @@
import * as vscode from 'vscode';
export class SampleKernel {
private readonly _id = 'test-notebook-renderer-kernel';
private readonly _label = 'Sample Notebook Kernel';
private readonly _supportedLanguages = ['json'];
private _executionOrder = 0;
private readonly _controller: vscode.NotebookController;
constructor() {
this._controller = vscode.notebooks.createNotebookController(this._id,
'test-notebook-renderer',
this._label);
this._controller.supportedLanguages = this._supportedLanguages;
this._controller.supportsExecutionOrder = true;
this._controller.executeHandler = this._executeAll.bind(this);
}
dispose(): void {
this._controller.dispose();
}
private _executeAll(cells: vscode.NotebookCell[], _notebook: vscode.NotebookDocument, _controller: vscode.NotebookController): void {
for (const cell of cells) {
this._doExecution(cell);
}
}
private async _doExecution(cell: vscode.NotebookCell): Promise<void> {
const execution = this._controller.createNotebookCellExecution(cell);
execution.executionOrder = ++this._executionOrder;
execution.start(Date.now());
try {
execution.replaceOutput([new vscode.NotebookCellOutput([
vscode.NotebookCellOutputItem.json(JSON.parse(cell.document.getText()), "x-application/sample-json-renderer"),
vscode.NotebookCellOutputItem.json(JSON.parse(cell.document.getText()))
])]);
execution.end(true, Date.now());
} catch (err) {
execution.replaceOutput([new vscode.NotebookCellOutput([
vscode.NotebookCellOutputItem.error(err as Error)
])]);
execution.end(false, Date.now());
}
}
}

View File

@ -0,0 +1,15 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import { SampleKernel } from './controller';
import { SampleContentSerializer } from './provider';
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.workspace.registerNotebookSerializer(
'test-notebook-renderer', new SampleContentSerializer(), { transientOutputs: true }
),
new SampleKernel()
);
}

View File

@ -0,0 +1,58 @@
import * as vscode from 'vscode';
import { TextDecoder, TextEncoder } from 'util';
/**
* An ultra-minimal sample provider that lets the user type in JSON, and then
* outputs JSON cells. The outputs are transient and not saved to notebook file on disk.
*/
interface RawNotebookData {
cells: RawNotebookCell[]
}
interface RawNotebookCell {
language: string;
value: string;
kind: vscode.NotebookCellKind;
editable?: boolean;
}
export class SampleContentSerializer implements vscode.NotebookSerializer {
public readonly label: string = 'My Sample Content Serializer';
public async deserializeNotebook(data: Uint8Array, token: vscode.CancellationToken): Promise<vscode.NotebookData> {
const contents = new TextDecoder().decode(data); // convert to String
// Read file contents
let raw: RawNotebookData;
try {
raw = <RawNotebookData>JSON.parse(contents);
} catch {
raw = { cells: [] };
}
// Create array of Notebook cells for the VS Code API from file contents
const cells = raw.cells.map(item => new vscode.NotebookCellData(
item.kind,
item.value,
item.language
));
return new vscode.NotebookData(cells);
}
public async serializeNotebook(data: vscode.NotebookData, token: vscode.CancellationToken): Promise<Uint8Array> {
// Map the Notebook data into the format we want to save the Notebook data as
const contents: RawNotebookData = { cells: [] };
for (const cell of data.cells) {
contents.cells.push({
kind: cell.kind,
language: cell.languageId,
value: cell.value
});
}
return new TextEncoder().encode(JSON.stringify(contents));
}
}

View File

@ -0,0 +1,8 @@
{
"extends": "../tsconfig-base.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "../../out/extension",
},
"references": []
}

View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2019",
"lib": [
"ES2019"
],
"types": ["node"],
"moduleResolution": "node",
"sourceMap": true,
"strict": true /* enable all strict type-checking options */
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
}
}

View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./src/client"
},
{
"path": "./src/extension"
}
]
}

View File

@ -0,0 +1,79 @@
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const { DefinePlugin } = require('webpack');
const path = require('path');
const outputFilename = 'index.js';
const devServerPort = 8111;
module.exports = (env, argv) => ({
mode: argv.mode,
devtool: argv.mode === 'production' ? false : 'inline-source-map',
entry: './src/client/index.ts',
output: {
path: path.join(__dirname, 'out', 'client'),
filename: outputFilename,
publicPath: '',
libraryTarget: 'module',
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.css'],
},
experiments: {
outputModule: true,
},
module: {
rules: [
// Allow importing ts(x) files:
{
test: /\.tsx?$/,
loader: 'ts-loader',
options: {
configFile: 'src/client/tsconfig.json',
// transpileOnly enables hot-module-replacement
transpileOnly: true,
compilerOptions: {
// Overwrite the noEmit from the client's tsconfig
noEmit: false,
},
},
},
// Allow importing CSS modules:
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: true,
},
},
],
},
],
},
devServer: {
port: devServerPort,
hot: true,
// Disable the host check, otherwise the bundle running in VS Code won't be
// able to connect to the dev server
disableHostCheck: true,
writeToDisk: true,
headers: { 'Access-Control-Allow-Origin': '*' },
},
plugins: [
new ForkTsCheckerWebpackPlugin({
typescript: {
tsconfig: 'src/client/tsconfig.json',
},
}),
new DefinePlugin({
// Path from the output filename to the output directory
__webpack_relative_entrypoint_to_root__: JSON.stringify(
path.posix.relative(path.posix.dirname(`/${outputFilename}`), '/'),
),
scriptUrl: 'import.meta.url',
}),
],
});