mirror of
https://github.com/microsoft/vscode-extension-samples.git
synced 2026-06-13 07:10:26 +08:00
Jupyter server provider sample
This commit is contained in:
23
jupyter-server-provider-sample/.eslintrc.js
Normal file
23
jupyter-server-provider-sample/.eslintrc.js
Normal 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,
|
||||
}
|
||||
};
|
||||
6
jupyter-server-provider-sample/.vscode/extensions.json
vendored
Normal file
6
jupyter-server-provider-sample/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"ms-toolsai.jupyter"
|
||||
]
|
||||
}
|
||||
22
jupyter-server-provider-sample/.vscode/launch.json
vendored
Normal file
22
jupyter-server-provider-sample/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// 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"
|
||||
],
|
||||
"trace": true,
|
||||
// "preLaunchTask": "npm: watch"
|
||||
}
|
||||
]
|
||||
}
|
||||
6
jupyter-server-provider-sample/.vscode/settings.json
vendored
Normal file
6
jupyter-server-provider-sample/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"search.exclude": {
|
||||
"out": true
|
||||
},
|
||||
"typescript.tsc.autoDetect": "off"
|
||||
}
|
||||
13
jupyter-server-provider-sample/.vscode/tasks.json
vendored
Normal file
13
jupyter-server-provider-sample/.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "compile",
|
||||
"problemMatcher": ["$tsc"],
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
12
jupyter-server-provider-sample/.vscodeignore
Normal file
12
jupyter-server-provider-sample/.vscodeignore
Normal file
@ -0,0 +1,12 @@
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
out/test/**
|
||||
src/**
|
||||
.gitignore
|
||||
.yarnrc
|
||||
vsc-extension-quickstart.md
|
||||
**/tsconfig.json
|
||||
**/.eslintrc.json
|
||||
**/*.map
|
||||
**/*.ts
|
||||
**/*.tsbuildinfo
|
||||
16
jupyter-server-provider-sample/README.md
Normal file
16
jupyter-server-provider-sample/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# jupyter-servedr-provider-sample
|
||||
|
||||
This is a very simple extension sample demonstrating the use of the Jupyter Extension API allowing other extensions to contribute [Jupyter](https://jupyter.org/) Servers via the Kernel Picker for Jupyter notebooks:
|
||||
|
||||
- The sample extension finds Jupyter Servers running locally on the current machine
|
||||
- This list of servers is then provided to the Jupyter extension via the Jupyter Extension API
|
||||
- Upon opening a notebook and selecting a kernel the option `Local JupyterLab Servers...` will display the above servers.
|
||||
- From there, the user can select a kernel and run code in the notebook against one of the local kernels.
|
||||
|
||||
## Running this sample
|
||||
|
||||
1. `cd jupyter-server-provider-sample`
|
||||
1. `code .`: Open the folder in VS Code
|
||||
1. Hit `F5` to build+debug
|
||||
1. Select the kernel Picker for a notebook and select the option `Local JupyterLab Servers...`
|
||||
1. To see servers show up in this list, start Jupyter Lab or Jupyter Notebook outside VS Code or from within the integrated terminal (e.g. [jupyter lab](https://jupyterlab.readthedocs.io/en/stable/getting_started/starting.html))
|
||||
1821
jupyter-server-provider-sample/package-lock.json
generated
Normal file
1821
jupyter-server-provider-sample/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
jupyter-server-provider-sample/package.json
Normal file
36
jupyter-server-provider-sample/package.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "jupyter-server-provider-sample",
|
||||
"displayName": "notebook-serializer-sample",
|
||||
"description": "Jupyter Notebook Kernel Picker with custom servers using the Jupyter API sample",
|
||||
"publisher": "vscode-samples",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"vscode": "^1.82.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onNotebook:jupyter-notebook",
|
||||
"onNotebook:interactive"
|
||||
],
|
||||
"extensionDependencies": [
|
||||
"ms-toolsai.jupyter"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"compile": "tsc -b",
|
||||
"lint": "eslint src --ext ts",
|
||||
"watch": "tsc -b --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "14.x",
|
||||
"@types/vscode": "^1.82.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
||||
"@typescript-eslint/parser": "^6.7.0",
|
||||
"@vscode/jupyter-extension": "^0.1.0",
|
||||
"eslint": "^7.27.0",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
||||
128
jupyter-server-provider-sample/src/extension.ts
Normal file
128
jupyter-server-provider-sample/src/extension.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import {
|
||||
CancellationToken,
|
||||
ExtensionContext,
|
||||
ProgressLocation,
|
||||
Uri,
|
||||
extensions,
|
||||
window,
|
||||
} from 'vscode';
|
||||
import { Jupyter } from '@vscode/jupyter-extension';
|
||||
import { findLocallyRunningServers } from './jupyter';
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
const jupyterExt = extensions.getExtension<Jupyter>('ms-toolsai.jupyter');
|
||||
if (!jupyterExt) {
|
||||
throw new Error('Jupyter Extension not installed');
|
||||
}
|
||||
if (!jupyterExt.isActive) {
|
||||
jupyterExt.activate();
|
||||
}
|
||||
|
||||
const jupyterLab = jupyterExt.exports.createJupyterServerCollection(
|
||||
`${context.extension.id}:lab`,
|
||||
'Local JupyterLab Servers...',
|
||||
{
|
||||
provideJupyterServers: () => provideJupyterServers('lab'),
|
||||
resolveJupyterServer: (server) => server,
|
||||
}
|
||||
);
|
||||
context.subscriptions.push(jupyterLab);
|
||||
// // Commands are optional.
|
||||
jupyterLab.commandProvider = {
|
||||
provideCommands: () => {
|
||||
return [
|
||||
{
|
||||
label: 'Start Jupyter Lab',
|
||||
description: 'Start a new server in the terminal',
|
||||
},
|
||||
];
|
||||
},
|
||||
handleCommand: async (command, token) => {
|
||||
if (command.label === 'Start Jupyter Lab') {
|
||||
return startJupyterInTerminal('lab', token);
|
||||
}
|
||||
},
|
||||
};
|
||||
// Each collection is treated as a logical collection of Jupyter Servers.
|
||||
// Thus an extension can contribute more than one collection of servers.
|
||||
const jupyterNotebook = jupyterExt.exports.createJupyterServerCollection(
|
||||
`${context.extension.id}:notebook`,
|
||||
'Local Jupyter Notebook Servers...',
|
||||
{
|
||||
provideJupyterServers: () => provideJupyterServers('notebook'),
|
||||
resolveJupyterServer: (server) => server,
|
||||
}
|
||||
);
|
||||
context.subscriptions.push(jupyterNotebook);
|
||||
// Commands are optional.
|
||||
jupyterNotebook.commandProvider = {
|
||||
provideCommands: () => {
|
||||
return [
|
||||
{
|
||||
label: 'Start Jupyter Notebook',
|
||||
description: 'Start a new server in the terminal',
|
||||
},
|
||||
];
|
||||
},
|
||||
handleCommand: async (command, token) => {
|
||||
if (command.label === 'Start Jupyter Notebook') {
|
||||
return startJupyterInTerminal('notebook', token);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function provideJupyterServers(type: 'lab' | 'notebook') {
|
||||
const servers = await findLocallyRunningServers(type);
|
||||
return servers.map((server) => {
|
||||
return {
|
||||
id: `${server.pid}:${server.port}`,
|
||||
label: server.url,
|
||||
connectionInformation: {
|
||||
baseUrl: Uri.parse(server.url),
|
||||
token: server.token,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
async function startJupyterInTerminal(type: 'lab' | 'notebook', token: CancellationToken) {
|
||||
return await window.withProgress(
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Starting Jupyter Lab',
|
||||
},
|
||||
async () => {
|
||||
const servers = await findLocallyRunningServers(type);
|
||||
const existingPids = new Set(servers.map((s) => s.pid));
|
||||
const terminal = window.createTerminal(
|
||||
type === 'lab' ? 'Jupyter Lab' : 'Jupyter Notebook'
|
||||
);
|
||||
terminal.show();
|
||||
terminal.sendText(`jupyter ${type}`);
|
||||
// Wait for 5 seconds, then give up, this is only a sample.
|
||||
await new Promise((resolve) => setTimeout(resolve, 5_000));
|
||||
|
||||
const newServers = (await findLocallyRunningServers(type)).filter(
|
||||
(s) => !existingPids.has(s.pid)
|
||||
);
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
if (newServers.length) {
|
||||
const server = newServers[0];
|
||||
return {
|
||||
id: `${server.pid}:${server.port}`,
|
||||
label: new URL(server.url).hostname,
|
||||
connectionInformation: {
|
||||
baseUrl: Uri.parse(server.url),
|
||||
token: server.token,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
window.showInformationMessage(
|
||||
'Timeout waiting for Jupyter to start in the terminal'
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
108
jupyter-server-provider-sample/src/jupyter.ts
Normal file
108
jupyter-server-provider-sample/src/jupyter.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const homeDir = getUserHomeDir();
|
||||
|
||||
export async function findLocallyRunningServers(type: 'lab' | 'notebook') {
|
||||
const runtimeDir = path.join(getDataDirectory(), 'runtime');
|
||||
const files = await promisify(fs.readdir)(runtimeDir);
|
||||
const servers: {
|
||||
url: string;
|
||||
token: string;
|
||||
root_dir: string;
|
||||
pid: number;
|
||||
port: number;
|
||||
}[] = [];
|
||||
const prefix = type === 'lab' ? 'jpserver-' : 'nbserver';
|
||||
await Promise.all(
|
||||
files.map(async (file) => {
|
||||
if (!file.startsWith(prefix) || !file.endsWith('.json')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contents = await promisify(fs.readFile)(path.join(runtimeDir, file)).then(
|
||||
(c) => c.toString()
|
||||
);
|
||||
const json: {
|
||||
url: string;
|
||||
token: string;
|
||||
root_dir: string;
|
||||
pid: number;
|
||||
port: number;
|
||||
} = JSON.parse(contents);
|
||||
if (!json.token) {
|
||||
console.warn(`Ignoring url ${json.url} as it does not contain a token`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
process.kill(json.pid, 0);
|
||||
servers.push(json);
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return servers;
|
||||
}
|
||||
|
||||
function getDataDirectory() {
|
||||
if (process.env['JUPYTER_DATA_DIR']) {
|
||||
return path.normalize(process.env['JUPYTER_DATA_DIR']);
|
||||
}
|
||||
switch (getOSType()) {
|
||||
case 'osx':
|
||||
return path.join(homeDir, 'Library', 'Jupyter');
|
||||
case 'windows': {
|
||||
const appData = process.env['APPDATA']
|
||||
? path.normalize(process.env['APPDATA'])
|
||||
: '';
|
||||
if (appData) {
|
||||
return path.join(appData, 'jupyter');
|
||||
}
|
||||
const configDir = getJupyterConfigDir();
|
||||
if (configDir) {
|
||||
return path.join(configDir, 'data');
|
||||
}
|
||||
return path.join(homeDir, 'Library', 'Jupyter');
|
||||
}
|
||||
default: {
|
||||
// Linux, non-OS X Unix, AIX, etc.
|
||||
const xdgDataHome = process.env['XDG_DATA_HOME']
|
||||
? path.normalize(process.env['XDG_DATA_HOME'])
|
||||
: path.join(homeDir, '.local', 'share');
|
||||
return path.join(xdgDataHome, 'jupyter');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getJupyterConfigDir() {
|
||||
if (process.env['JUPYTER_CONFIG_DIR']) {
|
||||
return path.normalize(process.env['JUPYTER_CONFIG_DIR']);
|
||||
}
|
||||
return path.join(homeDir, '.jupyter');
|
||||
}
|
||||
|
||||
function getOSType() {
|
||||
const platform = process.platform;
|
||||
if (/^win/.test(platform)) {
|
||||
return 'windows';
|
||||
} else if (/^darwin/.test(platform)) {
|
||||
return 'osx';
|
||||
} else {
|
||||
return 'linux';
|
||||
}
|
||||
}
|
||||
|
||||
function getUserHomeDir() {
|
||||
const homePath = os.homedir();
|
||||
if (getOSType() === 'windows') {
|
||||
return process.env['USERPROFILE'] || homePath;
|
||||
}
|
||||
const homeVar = process.env['HOME'] || process.env['HOMEPATH'] || homePath;
|
||||
|
||||
// Make sure if linux, it uses linux separators
|
||||
return homeVar.replace(/\\/g, '/');
|
||||
}
|
||||
17
jupyter-server-provider-sample/tsconfig.json
Normal file
17
jupyter-server-provider-sample/tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2020",
|
||||
"lib": [
|
||||
"es2020"
|
||||
],
|
||||
"outDir": "out",
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"rootDir": "src"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".vscode-test"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user