├── .editorconfig ├── .eslintrc.json ├── .github └── workflows │ ├── ci.yml │ └── pr.yml ├── .gitignore ├── .nvmrc ├── .prettierrc.js ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── ThirdPartyNotices-Repository.txt ├── build ├── azure-pipeline.insider.yml ├── azure-pipeline.stable.yml ├── azure-pipelines.pr.yml ├── ci │ ├── pr.yaml │ └── templates │ │ ├── globals.yml │ │ ├── jobs │ │ └── build_compile.yml │ │ └── steps │ │ ├── build.yml │ │ ├── compile.yml │ │ ├── dependencies.yml │ │ ├── initialization.yml │ │ └── test.yml ├── constants.js ├── postInstall.js └── webpack │ ├── ansi-to-react.js │ ├── common.js │ ├── loaders │ ├── jsonloader.js │ └── remarkLoader.js │ ├── plugins │ └── less-plugin-base64.js │ ├── postBuildHook.js │ ├── webpack.client.config.js │ ├── webpack.config.js │ └── webpack.extension.config.js ├── icon.png ├── images └── sample.png ├── package-lock.json ├── package.json ├── src ├── client │ ├── builtinRendererHooks.ts │ ├── clipboard.ts │ ├── constants.ts │ ├── helpers.ts │ ├── index.css │ ├── index.tsx │ ├── markdown.ts │ ├── preload.ts │ ├── render.tsx │ ├── transforms.tsx │ ├── tsconfig.json │ ├── types │ │ └── @nteract │ │ │ ├── transform-dataresource.d.ts │ │ │ ├── transform-geojson.d.ts │ │ │ ├── transform-model-debug.d.ts │ │ │ ├── transform-plotly.d.ts │ │ │ ├── transform-vsdom.d.ts │ │ │ └── transforms.d.ts │ └── vegaRenderer.ts ├── extension │ ├── constants.ts │ ├── index.ts │ └── tsconfig.json └── tsconfig-base.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Tab indentation 7 | [*] 8 | indent_style = space 9 | indent_size = 4 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | # The indent size used in the `package.json` file cannot be changed 14 | # https://github.com/npm/npm/pull/3180#issuecomment-16336516 15 | [{.travis.yml,npm-shrinkwrap.json,package.json}] 16 | indent_style = space 17 | indent_size = 4 18 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "ecmaVersion": 6, 10 | "sourceType": "module" 11 | }, 12 | "plugins": [ 13 | "@typescript-eslint", 14 | "prettier" 15 | ], 16 | "extends": [ 17 | "eslint:recommended", 18 | "plugin:@typescript-eslint/eslint-recommended", 19 | "plugin:@typescript-eslint/recommended", 20 | "prettier" 21 | ], 22 | "rules": { 23 | "no-throw-literal": "warn", 24 | "no-console": 0, 25 | "no-cond-assign": 0, 26 | "no-unused-vars": [ 27 | "error", 28 | { 29 | "argsIgnorePattern": "^_" 30 | } 31 | ], 32 | "@typescript-eslint/no-unused-vars": [ 33 | "error", 34 | { 35 | "argsIgnorePattern": "^_" 36 | } 37 | ], 38 | "@typescript-eslint/explicit-module-boundary-types": "off", 39 | "no-extra-semi": "warn", 40 | "semi": "warn", 41 | "prettier/prettier": "error" 42 | }, 43 | "overrides": [ 44 | { 45 | "files": [ 46 | "**/*.test.ts", 47 | "**/*.test.tsx" 48 | ], 49 | "env": { 50 | "mocha": true 51 | } 52 | }, 53 | { 54 | "files": [ 55 | "**/client/**/*.ts*" 56 | ], 57 | "env": { 58 | "browser": true 59 | } 60 | }, 61 | { 62 | "files": [ 63 | "build/**/*.js" 64 | ], 65 | "rules": { 66 | "@typescript-eslint/no-var-requires": "off" 67 | } 68 | }, 69 | { 70 | "files": [ 71 | "build/**/plugins/**/*.js" 72 | ], 73 | "rules": { 74 | "no-unused-vars": "off", 75 | "@typescript-eslint/no-unused-vars": "off", 76 | "@typescript-eslint/no-empty-function": "off" 77 | } 78 | }, 79 | { 80 | "files": [ 81 | "src/**/*.d.ts" 82 | ], 83 | "rules": { 84 | "no-unused-vars": "off", 85 | "@typescript-eslint/no-explicit-any": "off", 86 | "@typescript-eslint/ban-types": "off", 87 | "@typescript-eslint/adjacent-overload-signatures": "off" 88 | } 89 | } 90 | ] 91 | } 92 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This yml is used for PRs, pre-release, and release build. 2 | # We use the github.event_name to determine what started the workflow to determine which 3 | # situation we are in. 4 | 5 | name: CI 6 | 7 | permissions: 8 | deployments: write 9 | 10 | on: 11 | pull_request: 12 | branches: 13 | - main 14 | - 'release/*' 15 | check_run: 16 | types: [rerequested, requested_action] 17 | # push: 18 | # branches: 19 | # - main 20 | # - 'release/*' 21 | workflow_dispatch: 22 | 23 | env: 24 | NODE_VERSION: 18.15.0 25 | NPM_VERSION: 9.5.0 26 | 27 | jobs: 28 | build: 29 | name: CI 30 | runs-on: ubuntu-latest 31 | if: github.repository == 'microsoft/vscode-notebook-renderers' 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v4 35 | 36 | - name: Use Node ${{env.NODE_VERSION}} 37 | uses: actions/setup-node@v4 38 | with: 39 | node-version: ${{env.NODE_VERSION}} 40 | 41 | - name: Use Npm ${{env.NPM_VERSION}} 42 | run: npm i -g npm@${{env.NPM_VERSION}} 43 | 44 | - run: npm i -g @vscode/vsce 45 | 46 | - run: npm ci 47 | name: Install dependencies 48 | 49 | - run: npm run lint 50 | name: 'Lint' 51 | 52 | - run: npm run lint-format 53 | name: 'Code Format' 54 | 55 | - name: Build VSIX 56 | run: npm run package 57 | 58 | - uses: actions/upload-artifact@v4 59 | with: 60 | name: 'ms-notebook-renderers.vsix' 61 | path: 'ms-notebook-renderers.vsix' 62 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | # This yml is used for PRs, pre-release, and release build. 2 | # We use the github.event_name to determine what started the workflow to determine which 3 | # situation we are in. 4 | 5 | name: PR 6 | 7 | permissions: 8 | deployments: write 9 | 10 | on: 11 | pull_request: 12 | branches: 13 | - main 14 | - 'release/*' 15 | check_run: 16 | types: [rerequested, requested_action] 17 | # push: 18 | # branches: 19 | # - main 20 | # - 'release/*' 21 | workflow_dispatch: 22 | 23 | env: 24 | NODE_VERSION: 18.15.0 25 | NPM_VERSION: 9.5.0 26 | 27 | jobs: 28 | build: 29 | name: PR 30 | runs-on: ubuntu-latest 31 | if: github.repository == 'microsoft/vscode-notebook-renderers' 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v4 35 | 36 | - name: Use Node ${{env.NODE_VERSION}} 37 | uses: actions/setup-node@v4 38 | with: 39 | node-version: ${{env.NODE_VERSION}} 40 | 41 | - name: Use Npm ${{env.NPM_VERSION}} 42 | run: npm i -g npm@${{env.NPM_VERSION}} 43 | 44 | - run: npm i -g @vscode/vsce 45 | 46 | - run: npm ci 47 | name: Install dependencies 48 | 49 | - run: npm run lint 50 | name: 'Lint' 51 | 52 | - run: npm run lint-format 53 | name: 'Code Format' 54 | 55 | - name: Build VSIX 56 | run: npm run package 57 | 58 | - uses: actions/upload-artifact@v4 59 | with: 60 | name: 'ms-notebook-renderers.vsix' 61 | path: 'ms-notebook-renderers.vsix' 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | **/.DS_Store 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.17.0 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | printWidth: 120, 4 | tabWidth: 4, 5 | endOfLine: 'auto', 6 | trailingComma: 'none', 7 | overrides: [ 8 | { 9 | files: ['*.yml', '*.yaml'], 10 | options: { 11 | tabWidth: 2 12 | } 13 | } 14 | ] 15 | }; 16 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint", 6 | "editorconfig.editorconfig", 7 | "esbenp.prettier-vscode" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Web Extension in VS Code", 10 | "type": "pwa-extensionHost", 11 | "debugWebWorkerHost": true, 12 | "request": "launch", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}", 15 | "--extensionDevelopmentKind=web" 16 | ] 17 | }, 18 | { 19 | "name": "Run Extension", 20 | "type": "extensionHost", 21 | "request": "launch", 22 | "runtimeExecutable": "${execPath}", 23 | "args": [ 24 | "--extensionDevelopmentPath=${workspaceFolder}" 25 | ], 26 | "outFiles": [ 27 | "${workspaceFolder}/out/**/*.js" 28 | ], 29 | "preLaunchTask": "${defaultBuildTask}" 30 | }, 31 | { 32 | "name": "Extension Tests", 33 | "type": "extensionHost", 34 | "request": "launch", 35 | "runtimeExecutable": "${execPath}", 36 | "args": [ 37 | "--extensionDevelopmentPath=${workspaceFolder}", 38 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 39 | ], 40 | "outFiles": [ 41 | "${workspaceFolder}/out/test/**/*.js" 42 | ], 43 | "preLaunchTask": "${defaultBuildTask}" 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files 5 | "**/node_modules": false, 6 | ".vscode-test": true 7 | }, 8 | "search.exclude": { 9 | "out": true, // set this to false to include "out" folder in search results 10 | "**/node_modules": true, 11 | ".vscode-test": true 12 | }, 13 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 14 | "typescript.tsc.autoDetect": "off", 15 | "editor.defaultFormatter": "esbenp.prettier-vscode", 16 | "[typescript]": { 17 | "editor.formatOnSave": true 18 | }, 19 | "[javascript]": { 20 | "editor.formatOnSave": true, 21 | "editor.defaultFormatter": "esbenp.prettier-vscode" 22 | }, 23 | "typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version 24 | "typescript.preferences.quoteStyle": "single", 25 | "javascript.preferences.quoteStyle": "single", 26 | "typescriptHero.imports.stringQuoteStyle": "'", 27 | "prettier.printWidth": 120, 28 | "prettier.singleQuote": true, 29 | "editor.codeActionsOnSave": { 30 | "source.fixAll.eslint": "explicit" 31 | }, 32 | "typescript.preferences.importModuleSpecifier": "relative", 33 | "debug.javascript.usePreview": false, 34 | "git.branchProtection": [ 35 | "main", 36 | "release*" 37 | ], 38 | "git.branchProtectionPrompt": "alwaysCommitToNewBranch", 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "label": "Build (dev)", 9 | "script": "dev", 10 | "isBackground": true, 11 | "problemMatcher": [ 12 | "$tsc-watch", 13 | "$ts-checker-webpack-watch" 14 | ], 15 | "presentation": { 16 | "reveal": "never" 17 | }, 18 | "group": { 19 | "kind": "build", 20 | "isDefault": true 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "label": "Build (release)", 26 | "script": "vscode:prepublish", 27 | "problemMatcher": [ 28 | "$ts-checker-webpack" 29 | ], 30 | "group": "build" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .github/** 2 | .vscode/** 3 | .vscode-test/** 4 | build/** 5 | out/test/** 6 | src/** 7 | .gitignore 8 | .nvmrc 9 | node_modules 10 | vsc-extension-quickstart.md 11 | **/tsconfig.json 12 | **/tsconfig-base.json 13 | **/.gitignore 14 | **/.editorconfig 15 | **/.eslintrc.json 16 | **/.prettierrc.js 17 | **/*.ts 18 | **/*.stats.json 19 | **/*.analyzer.html 20 | **/*.map 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Deprecated 4 | 5 | This changelog is deprecated. Here is where you can find details about the latest updates to this extension: 6 | - Highlighted features for the latest release are described in the VS Code release notes, under the "Contributions to extensions" section: https://code.visualstudio.com/updates 7 | - All issues and code changes can be found by searching our Github repo under the latest milestone. [Example from November 2022](https://github.com/microsoft/vscode-jupyter/issues?q=is%3Aclosed+milestone%3A%22November+2022%22+) 8 | 9 | ## 1.0.12 10 | * Ship jQuery and requirejs as pre-load scripts so that all outputs have access to these scripts. 11 | 12 | ## 1.0.10 13 | * Add support for VegaLite 5. 14 | * Fixes to rendering of Vega 5, VegaLite 3 and VegaLite 4. 15 | * Fixes to rendering of JavaScript mime types to ensure the right variables are available and right context is setup. 16 | * Ensure `jQuery` is available when rendering JavaScript mime types. 17 | * Ensure outputs generated using `IPython.display.code` are displayed with the right syntax highlighting. 18 | 19 | ## 1.0.7 20 | * Update plotly to version 2.11.1 21 | * Update npm packages. 22 | 23 | ## 1.0.6 24 | * Removed rendering of text/latex in favor of built-in support. 25 | 26 | ## 1.0.5 27 | * Updated to use Plotly version 2.7.0 28 | 29 | ## 1.0.4 30 | * Updated to use Plotly version 2.6.4 31 | 32 | ### Thanks 33 | 34 | Thanks to the following projects which we fully rely on to provide some of 35 | our features: 36 | 37 | - [Python Extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) 38 | 39 | Also thanks to the various projects we provide integrations with which help 40 | make this extension useful: 41 | 42 | - [Jupyter](https://jupyter.org/): 43 | [Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/?badge=latest), 44 | [ipywidgets](https://ipywidgets.readthedocs.io/en/latest/), 45 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Notebook Renderers extension for Visual Studio Code 2 | 3 | 4 | ## Contributing a pull request 5 | 6 | ### Prerequisites 7 | 8 | 1. [Node.js](https://nodejs.org/) 18.15.0 9 | 1. npm 9.5.0 10 | 1. Windows, macOS, or Linux 11 | 1. [Visual Studio Code](https://code.visualstudio.com/) 12 | 1. The following VS Code extensions: 13 | - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 14 | - [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 15 | - [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) 16 | 17 | ### Setup 18 | 19 | ```shell 20 | git clone https://github.com/Microsoft/vscode-notebook-renderers 21 | cd vscode-notebook-renderers 22 | npm ci 23 | ``` 24 | 25 | If you see warnings that `The engine "vscode" appears to be invalid.`, you can ignore these. 26 | 27 | ### Incremental Build 28 | 29 | Run the `Build` build Tasks from the [Run Build Task...](https://code.visualstudio.com/docs/editor/tasks) command picker (short cut `CTRL+SHIFT+B` or `⇧⌘B`). This will leave build task running in the background and which will re-run as files are edited and saved. You can see the output from either task in the Terminal panel (use the selector to choose which output to look at). 30 | 31 | For incremental builds you can use the following commands depending on your needs: 32 | 33 | ```shell 34 | npm run dev 35 | ``` 36 | 37 | ### Errors and Warnings 38 | 39 | TypeScript errors and warnings will be displayed in the `Problems` window of Visual Studio Code. 40 | 41 | ## Local Build 42 | 43 | Steps to build the extension on your machine once you've cloned the repo: 44 | 45 | ```bash 46 | > npm install -g @vscode/vsce 47 | > npm ci 48 | > npm run package 49 | ``` 50 | 51 | 52 | ## Local Debugging 53 | 54 | 1. From the debug panel select `Run Extension` 55 | Note: This will automatically start the incremental build task. 56 | Optionally, use the command `Debug: Select and Start Debugging -> Run Extension` 57 | 2. Once VS Code launches, open a notebook file (`.ipynb`) with output for one of the mime types supported by this extension (such as `application/vnd.vega.v2+json`) 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Renderers for Jupyter Notebooks in Visual Studio Code 2 | 3 | A [Visual Studio Code](https://code.visualstudio.com/) extension that provides renderers for outputs of [Jupyter Notebooks](https://jupyter.org/). 4 | 5 | 6 | 7 | # Getting Started 8 | * Install this extension 9 | * Open a Jupyter Notebook in VS Code 10 | * Outputs of the following mime type will be rendered by this extension: 11 | * image/gif 12 | * image/png 13 | * image/jpeg 14 | * image/svg+xml 15 | * image/webp 16 | * application/geo+json 17 | * application/vdom.v1+json 18 | * application/vnd.dataresource+json 19 | * application/vnd.plotly.v1+json 20 | * application/vnd.vega.v2+json 21 | * application/vnd.vega.v3+json 22 | * application/vnd.vega.v4+json 23 | * application/vnd.vega.v5+json 24 | * application/vnd.vegalite.v1+json 25 | * application/vnd.vegalite.v2+json 26 | * application/vnd.vegalite.v3+json 27 | * application/vnd.vegalite.v4+json 28 | * application/vnd.vegalite.v5+json 29 | * application/x-nteract-model-debug+json 30 | * text/vnd.plotly.v1+html 31 | 32 | 33 | # Contributing 34 | 35 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 36 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 37 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 38 | 39 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 40 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 41 | provided by the bot. You will only need to do this once across all repos using our CLA. 42 | 43 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 44 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 45 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 46 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /ThirdPartyNotices-Repository.txt: -------------------------------------------------------------------------------- 1 | 2 | THIRD-PARTY SOFTWARE NOTICES AND INFORMATION 3 | Do Not Translate or Localize 4 | 5 | Microsoft Jupyter Notebook Renderers extension for Visual Studio Code incorporates third party material from the projects listed below. The original copyright notice and the license under which Microsoft received such third party material are set forth below. Microsoft reserves all other rights not expressly granted, whether by implication, estoppel or otherwise. 6 | 7 | 1. font-awesome (https://github.com/FortAwesome/Font-Awesome) 8 | 9 | %% font-awesome NOTICES, INFORMATION, AND LICENSE BEGIN HERE 10 | ========================================= 11 | Font Name - FontAwesome 12 | 13 | Font Awesome Free License 14 | ------------------------- 15 | 16 | Font Awesome Free is free, open source, and GPL friendly. You can use it for 17 | commercial projects, open source projects, or really almost whatever you want. 18 | Full Font Awesome Free license: https://fontawesome.com/license/free. 19 | 20 | # Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) 21 | In the Font Awesome Free download, the CC BY 4.0 license applies to all icons 22 | packaged as SVG and JS file types. 23 | 24 | # Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL) 25 | In the Font Awesome Free download, the SIL OFL license applies to all icons 26 | packaged as web and desktop font files. 27 | 28 | # Code: MIT License (https://opensource.org/licenses/MIT) 29 | In the Font Awesome Free download, the MIT license applies to all non-font and 30 | non-icon files. 31 | 32 | # Attribution 33 | Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font 34 | Awesome Free files already contain embedded comments with sufficient 35 | attribution, so you shouldn't need to do anything additional when using these 36 | files normally. 37 | 38 | We've kept attribution comments terse, so we ask that you do not actively work 39 | to remove them from files, especially code. They're a great way for folks to 40 | learn about Font Awesome. 41 | 42 | # Brand Icons 43 | All brand icons are trademarks of their respective owners. The use of these 44 | trademarks does not indicate endorsement of the trademark holder by Font 45 | Awesome, nor vice versa. **Please do not use brand logos for any purpose except 46 | to represent the company, product, or service to which they refer.** 47 | ========================================= 48 | END OF font-awesome NOTICES, INFORMATION, AND LICENSE 49 | -------------------------------------------------------------------------------- /build/azure-pipeline.insider.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - main 5 | pr: none 6 | 7 | resources: 8 | repositories: 9 | - repository: templates 10 | type: github 11 | name: microsoft/vscode-engineering 12 | ref: main 13 | endpoint: Monaco 14 | 15 | parameters: 16 | - name: publishExtension 17 | displayName: 🚀 Publish Extension 18 | type: boolean 19 | default: false 20 | 21 | extends: 22 | template: azure-pipelines/extension/pre-release.yml@templates 23 | parameters: 24 | ghCreateTag: false 25 | standardizedVersioning: true 26 | publishExtension: ${{ parameters.publishExtension }} 27 | buildSteps: 28 | - bash: npm i -g npm@9.5.0 29 | displayName: Install npm@9.5.0 30 | 31 | - script: npm ci 32 | displayName: Install dependencies 33 | 34 | - bash: npm run lint 35 | displayName: 'Lint' 36 | 37 | - bash: npm run lint-format 38 | displayName: 'Code Format' 39 | 40 | - script: npm run compile 41 | displayName: Compile 42 | 43 | tsa: 44 | config: 45 | areaPath: 'Visual Studio Code Jupyter Extensions' 46 | serviceTreeID: '14f24efd-b502-422a-9f40-09ea7ce9cf14' 47 | enabled: true 48 | -------------------------------------------------------------------------------- /build/azure-pipeline.stable.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - main 5 | pr: none 6 | 7 | resources: 8 | repositories: 9 | - repository: templates 10 | type: github 11 | name: microsoft/vscode-engineering 12 | ref: main 13 | endpoint: Monaco 14 | 15 | parameters: 16 | - name: publishExtension 17 | displayName: 🚀 Publish Extension 18 | type: boolean 19 | default: false 20 | 21 | extends: 22 | template: azure-pipelines/extension/stable.yml@templates 23 | parameters: 24 | publishExtension: ${{ parameters.publishExtension }} 25 | buildSteps: 26 | - bash: npm i -g npm@9.5.0 27 | displayName: Install npm@9.5.0 28 | 29 | - script: npm ci 30 | displayName: Install dependencies 31 | 32 | - bash: npm run lint 33 | displayName: 'Lint' 34 | 35 | - bash: npm run lint-format 36 | displayName: 'Code Format' 37 | 38 | - script: npm run compile 39 | displayName: Compile 40 | 41 | tsa: 42 | config: 43 | areaPath: 'Visual Studio Code Jupyter Extensions' 44 | serviceTreeID: '14f24efd-b502-422a-9f40-09ea7ce9cf14' 45 | enabled: true 46 | -------------------------------------------------------------------------------- /build/azure-pipelines.pr.yml: -------------------------------------------------------------------------------- 1 | # Notes: Only trigger a PR build for main and release, and skip build/rebuild 2 | # on changes in the news and .vscode folders. 3 | pr: 4 | autoCancel: true 5 | branches: 6 | include: ['main', 'release/*'] 7 | paths: 8 | exclude: ['/.vscode'] 9 | 10 | # Not the CI build for merges to main and release. 11 | trigger: none 12 | 13 | resources: 14 | repositories: 15 | - repository: templates 16 | type: github 17 | name: microsoft/vscode-engineering 18 | ref: main 19 | endpoint: Monaco 20 | 21 | extends: 22 | template: azure-pipelines/extension/stable.yml@templates 23 | parameters: 24 | buildSteps: 25 | - bash: npm i -g npm@9.5.0 26 | displayName: Install npm@9.5.0 27 | 28 | - script: npm ci 29 | displayName: Install dependencies 30 | 31 | - bash: npm run lint 32 | displayName: 'Lint' 33 | 34 | - bash: npm run lint-format 35 | displayName: 'Code Format' 36 | 37 | - script: npm run compile 38 | displayName: Compile 39 | -------------------------------------------------------------------------------- /build/ci/pr.yaml: -------------------------------------------------------------------------------- 1 | # PR Validation build. 2 | 3 | name: '$(Year:yyyy).$(Month).0.$(BuildID)-pr' 4 | 5 | # Notes: Only trigger a PR build for main and release, and skip build/rebuild 6 | # on changes in the news and .vscode folders. 7 | pr: 8 | autoCancel: true 9 | branches: 10 | include: ['main', 'release*'] 11 | paths: 12 | exclude: ['/.vscode'] 13 | 14 | # Not the CI build for merges to main and release. 15 | trigger: none 16 | 17 | # Variables that are available for the entire pipeline. 18 | variables: 19 | - template: templates/globals.yml 20 | 21 | stages: 22 | - stage: Build 23 | jobs: 24 | - template: templates/jobs/build_compile.yml 25 | 26 | # -------------------------------------------------------------------------------- /build/ci/templates/globals.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | NodeVersion: '14.16.0' # Check version of node used in VS Code. 3 | NpmVersion: '6.13.4' 4 | MOCHA_FILE: '$(Build.ArtifactStagingDirectory)/test-junit.xml' # All test files will write their JUnit xml output to this file, clobbering the last time it was written. 5 | MOCHA_REPORTER_JUNIT: true # Use the mocha-multi-reporters and send output to both console (spec) and JUnit (mocha-junit-reporter). 6 | CI_BRANCH_NAME: ${Build.SourceBranchName} 7 | npm_config_cache: $(Pipeline.Workspace)/.npm 8 | vmImageMacOS: 'macOS-10.15' 9 | TS_NODE_FILES: true # Temporarily enabled to allow using types from vscode.proposed.d.ts from ts-node (for tests). 10 | -------------------------------------------------------------------------------- /build/ci/templates/jobs/build_compile.yml: -------------------------------------------------------------------------------- 1 | # Overview: 2 | # Generic jobs template to compile and build extension 3 | 4 | jobs: 5 | - job: Compile 6 | pool: 7 | vmImage: 'ubuntu-latest' 8 | steps: 9 | - template: ../steps/compile.yml 10 | 11 | - job: Build 12 | pool: 13 | vmImage: 'ubuntu-latest' 14 | steps: 15 | - template: ../steps/build.yml 16 | 17 | - job: Test 18 | pool: 19 | vmImage: 'ubuntu-latest' 20 | steps: 21 | - template: ../steps/test.yml 22 | 23 | - job: Dependencies 24 | pool: 25 | vmImage: 'ubuntu-latest' 26 | steps: 27 | - template: ../steps/dependencies.yml 28 | 29 | - job: Hygiene 30 | pool: 31 | vmImage: 'ubuntu-latest' 32 | steps: 33 | - template: ../steps/initialization.yml 34 | parameters: 35 | workingDirectory: $(Build.SourcesDirectory) 36 | compile: 'false' 37 | installVSCEorNPX: 'false' 38 | 39 | - bash: npm run lint 40 | displayName: 'Lint' 41 | workingDirectory: $(Build.SourcesDirectory) 42 | 43 | - bash: npm run lint-format 44 | displayName: 'Code Format (TypeScript & JavaScript)' 45 | workingDirectory: $(Build.SourcesDirectory) 46 | -------------------------------------------------------------------------------- /build/ci/templates/steps/build.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - template: initialization.yml 3 | 4 | - bash: | 5 | npm run package 6 | displayName: 'Build VSIX' 7 | 8 | - task: CopyFiles@2 9 | inputs: 10 | contents: '*.vsix' 11 | targetFolder: $(Build.ArtifactStagingDirectory) 12 | displayName: 'Copy VSIX' 13 | 14 | - task: PublishBuildArtifacts@1 15 | inputs: 16 | pathtoPublish: $(Build.ArtifactStagingDirectory) 17 | artifactName: VSIX 18 | displayName: 'Publish VSIX to Artifacts' 19 | -------------------------------------------------------------------------------- /build/ci/templates/steps/compile.yml: -------------------------------------------------------------------------------- 1 | # Compiles the source 2 | 3 | steps: 4 | - template: initialization.yml 5 | 6 | - bash: npm run compile 7 | displayName: 'Compile' 8 | -------------------------------------------------------------------------------- /build/ci/templates/steps/dependencies.yml: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------------------------------------------------------- 2 | # Overview: 3 | # ----------------------------------------------------------------------------------------------------------------------------- 4 | # Set of steps used to validate dependencies for the extension. In a separate pipeline because this 5 | # can take a long time. 6 | # 7 | 8 | steps: 9 | - template: initialization.yml 10 | 11 | # - bash: npm run clean 12 | # displayName: 'Clean' 13 | 14 | # # This is a slow process, hence do this as a separate step 15 | # - bash: npm run checkDependencies 16 | # displayName: 'Check Dependencies' 17 | -------------------------------------------------------------------------------- /build/ci/templates/steps/initialization.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - bash: | 3 | printenv 4 | displayName: 'Show all env vars' 5 | condition: eq(variables['system.debug'], 'true') 6 | 7 | - task: NodeTool@0 8 | displayName: 'Use Node $(NodeVersion)' 9 | inputs: 10 | versionSpec: $(NodeVersion) 11 | 12 | - task: Npm@1 13 | displayName: 'Use NPM $(NpmVersion)' 14 | inputs: 15 | command: custom 16 | verbose: true 17 | customCommand: 'install -g npm@$(NpmVersion)' 18 | 19 | # See the help here on how to cache npm 20 | # https://docs.microsoft.com/en-us/azure/devops/pipelines/caching/?view=azure-devops#nodejsnpm 21 | - task: CacheBeta@0 22 | inputs: 23 | key: npm | $(Agent.OS) | package-lock.json 24 | path: $(npm_config_cache) 25 | restoreKeys: | 26 | npm | $(Agent.OS) 27 | displayName: Cache npm 28 | 29 | - task: Npm@1 30 | displayName: 'npm ci' 31 | inputs: 32 | workingDir: $(Build.SourcesDirectory) 33 | command: custom 34 | verbose: true 35 | customCommand: ci 36 | 37 | - bash: | 38 | npm install -g @vscode/vsce 39 | displayName: 'Install vsce' 40 | 41 | # https://code.visualstudio.com/api/working-with-extensions/continuous-integration#azure-pipelines 42 | - bash: | 43 | /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 44 | echo ">>> Started xvfb" 45 | displayName: Start xvfb 46 | condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) 47 | -------------------------------------------------------------------------------- /build/ci/templates/steps/test.yml: -------------------------------------------------------------------------------- 1 | # Compiles the source 2 | 3 | steps: 4 | - template: initialization.yml 5 | 6 | - bash: npm run compile 7 | displayName: 'Compile' 8 | 9 | # Temporarily disabled. 10 | # - bash: npm run test 11 | # displayName: 'Test' 12 | -------------------------------------------------------------------------------- /build/constants.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | const path = require('path'); 5 | exports.ExtensionRootDir = path.dirname(__dirname); 6 | exports.isWindows = /^win/.test(process.platform); 7 | exports.isCI = process.env.TF_BUILD !== undefined; 8 | -------------------------------------------------------------------------------- /build/postInstall.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | 7 | function fixPlotlyRenderer() { 8 | const filePath = path.join(__dirname, '..', 'node_modules/@nteract/transform-plotly/lib/index.js'); 9 | const textsToReplace = [ 10 | `this.Plotly.newPlot(this.plotDiv, figure.data, figure.layout);`, 11 | `const { data = {}, layout = {} } = figure;`, 12 | `return { data, layout };` 13 | ]; 14 | const textsToReplaceWith = [ 15 | `this.Plotly.newPlot(this.plotDiv, figure); //Replaced by postinstall`, 16 | `const { data = {}, layout = {}, frames = {}, config = {} } = figure;`, 17 | `return { data, layout, config, frames };` 18 | ]; 19 | const fileContents = fs.readFileSync(filePath, 'utf8').toString(); 20 | if (fileContents.indexOf(textsToReplace[0]) === -1 && fileContents.indexOf(textsToReplaceWith[0]) === -1) { 21 | throw new Error('Unable to find plotly fixup'); 22 | } 23 | let replacedFileContents = fileContents; 24 | textsToReplace.forEach((s, i) => { 25 | replacedFileContents = replacedFileContents.replace(s, textsToReplaceWith[i]); 26 | }); 27 | fs.writeFileSync(filePath, replacedFileContents); 28 | } 29 | 30 | fixPlotlyRenderer(); 31 | -------------------------------------------------------------------------------- /build/webpack/ansi-to-react.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // Not used, but required by nteract transforms. 5 | // Only used in rendering text/plain mimetypes, but we do not render those with nteract. 6 | module.exports = () => { 7 | /** */ 8 | }; 9 | -------------------------------------------------------------------------------- /build/webpack/common.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | const constants = require('../constants'); 5 | const webpack_bundle_analyzer = require('webpack-bundle-analyzer'); 6 | function getDefaultPlugins(name) { 7 | if (!constants.isCI && !process.argv.some((argv) => argv.includes('mode') && argv.includes('production'))) { 8 | return []; 9 | } 10 | return [ 11 | new webpack_bundle_analyzer.BundleAnalyzerPlugin({ 12 | analyzerMode: 'static', 13 | reportFilename: `${name}.analyzer.html`, 14 | generateStatsFile: true, 15 | statsFilename: `${name}.stats.json`, 16 | openAnalyzer: false 17 | }) 18 | ]; 19 | } 20 | exports.getDefaultPlugins = getDefaultPlugins; 21 | -------------------------------------------------------------------------------- /build/webpack/loaders/jsonloader.js: -------------------------------------------------------------------------------- 1 | // For some reason this has to be in commonjs format 2 | 3 | module.exports = function (source) { 4 | // Just inline the source and fix up defaults so that they don't 5 | // mess up the logic in the setOptions.js file 6 | return `module.exports = ${source}\nmodule.exports.default = false`; 7 | }; 8 | -------------------------------------------------------------------------------- /build/webpack/loaders/remarkLoader.js: -------------------------------------------------------------------------------- 1 | // For some reason this has to be in commonjs format 2 | 3 | module.exports = function (source) { 4 | // Just inline the source and fix up defaults so that they don't 5 | // mess up the logic in the setOptions.js file 6 | return `module.exports = ${source}\nmodule.exports.default = false`; 7 | }; 8 | -------------------------------------------------------------------------------- /build/webpack/plugins/less-plugin-base64.js: -------------------------------------------------------------------------------- 1 | // Most of this was based on https://github.com/less/less-plugin-inline-urls 2 | // License for this was included in the ThirdPartyNotices-Repository.txt 3 | const less = require('less'); 4 | 5 | class Base64MimeTypeNode { 6 | constructor() { 7 | this.value = 'image/svg+xml;base64'; 8 | this.type = 'Base64MimeTypeNode'; 9 | } 10 | 11 | eval(context) { 12 | return this; 13 | } 14 | } 15 | 16 | class Base64Visitor { 17 | constructor() { 18 | this.visitor = new less.visitors.Visitor(this); 19 | 20 | // Set to a preEval visitor to make sure this runs before 21 | // any evals 22 | this.isPreEvalVisitor = true; 23 | 24 | // Make sure this is a replacing visitor so we remove the old data. 25 | this.isReplacing = true; 26 | } 27 | 28 | run(root) { 29 | return this.visitor.visit(root); 30 | } 31 | 32 | visitUrl(URLNode, visitArgs) { 33 | // Return two new nodes in the call. One that has the mime type and other with the node. The data-uri 34 | // evaluator will transform this into a base64 string 35 | return new less.tree.Call( 36 | 'data-uri', 37 | [new Base64MimeTypeNode(), URLNode.value], 38 | URLNode.index || 0, 39 | URLNode.currentFileInfo 40 | ); 41 | } 42 | } 43 | /* 44 | * This was originally used to perform less on uris and turn them into base64 encoded so they can be loaded into 45 | * a webpack html. There's one caveat though. Less and webpack don't play well together. It runs the less at the root dir. 46 | * This means in order to use this in a less file, you need to qualify the urls as if they come from the root dir. 47 | * Example: 48 | * url("./foo.svg") 49 | * becomes 50 | * url("./src/datascience-ui/history-react/images/foo.svg") 51 | */ 52 | class Base64Plugin { 53 | constructor() {} 54 | 55 | install(less, pluginManager) { 56 | pluginManager.addVisitor(new Base64Visitor()); 57 | } 58 | 59 | printUsage() { 60 | console.log('Base64 Plugin. Add to your webpack.config.js as a plugin to convert URLs to base64 inline'); 61 | } 62 | } 63 | 64 | module.exports = Base64Plugin; 65 | -------------------------------------------------------------------------------- /build/webpack/postBuildHook.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | 6 | class PostBuildHookWebpackPlugin { 7 | apply(compiler) { 8 | compiler.hooks.assetEmitted.tap('MyExampleWebpackPlugin', (compilation) => { 9 | if (compilation === 'preload.js') { 10 | const file = path.join(__dirname, '..', '..', 'out', 'client_renderer', 'preload.js'); 11 | if (!fs.existsSync(file)) { 12 | throw new Error(`PostBuildHookWebpackPlugin failed, file does not exist (${file})`); 13 | } 14 | const requireFile = path.join(__dirname, '..', '..', 'node_modules', 'requirejs', 'require.js'); 15 | const jqueryFile = path.join(__dirname, '..', '..', 'node_modules', 'jquery', 'dist', 'jquery.min.js'); 16 | let requireFileContents = fs.readFileSync(requireFile).toString(); 17 | if (!requireFileContents.includes(undefDeclaration)) { 18 | throw new Error('Unable to update require.js'); 19 | } 20 | // Ensure jQuery, require and define are globally available. 21 | const declarations = [ 22 | 'globalThis.$ = $;', 23 | 'window.$ = $;', 24 | 'window.require=window.requirejs=requirejs; window.define=define;' 25 | ]; 26 | requireFileContents = `${requireFileContents 27 | .replace(invocationDeclaration, fixedInvocationDeclaration) 28 | .replace(undefDeclaration, fixedUndefDeclaration)}\n\n`; 29 | const newContents = `${requireFileContents}\n\n\n${fs.readFileSync(jqueryFile).toString()}\n\n\n${fs 30 | .readFileSync(file) 31 | .toString()}\n\n\n${declarations.join('\n')}`; 32 | fs.writeFileSync(file, newContents); 33 | } 34 | }); 35 | } 36 | } 37 | 38 | const invocationDeclaration = `}(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout)));`; 39 | const fixedInvocationDeclaration = `}(globalThis, (typeof setTimeout === 'undefined' ? undefined : setTimeout)));`; 40 | const undefDeclaration = ` localRequire.undef = function (id) { 41 | //Bind any waiting define() calls to this context, 42 | //fix for #408 43 | takeGlobalQueue(); 44 | 45 | var map = makeModuleMap(id, relMap, true), 46 | mod = getOwn(registry, id); 47 | 48 | mod.undefed = true; 49 | removeScript(id); 50 | 51 | delete defined[id]; 52 | delete urlFetched[map.url]; 53 | delete undefEvents[id]; 54 | 55 | //Clean queued defines too. Go backwards 56 | //in array so that the splices do not 57 | //mess up the iteration. 58 | eachReverse(defQueue, function(args, i) { 59 | if (args[0] === id) { 60 | defQueue.splice(i, 1); 61 | } 62 | }); 63 | delete context.defQueueMap[id]; 64 | 65 | if (mod) { 66 | //Hold on to listeners in case the 67 | //module will be attempted to be reloaded 68 | //using a different config. 69 | if (mod.events.defined) { 70 | undefEvents[id] = mod.events; 71 | } 72 | 73 | cleanRegistry(id); 74 | } 75 | };`; 76 | const fixedUndefDeclaration = ` localRequire.undef = function (id) { 77 | try { 78 | //Bind any waiting define() calls to this context, 79 | //fix for #408 80 | takeGlobalQueue(); 81 | 82 | var map = makeModuleMap(id, relMap, true), 83 | mod = getOwn(registry, id); 84 | 85 | mod.undefed = true; 86 | removeScript(id); 87 | 88 | delete defined[id]; 89 | delete urlFetched[map.url]; 90 | delete undefEvents[id]; 91 | 92 | //Clean queued defines too. Go backwards 93 | //in array so that the splices do not 94 | //mess up the iteration. 95 | eachReverse(defQueue, function(args, i) { 96 | if (args[0] === id) { 97 | defQueue.splice(i, 1); 98 | } 99 | }); 100 | delete context.defQueueMap[id]; 101 | 102 | if (mod) { 103 | //Hold on to listeners in case the 104 | //module will be attempted to be reloaded 105 | //using a different config. 106 | if (mod.events.defined) { 107 | undefEvents[id] = mod.events; 108 | } 109 | 110 | cleanRegistry(id); 111 | } 112 | } catch (e) { 113 | console.warn('require.undef in Notebook Renderer failed', id, e); 114 | } 115 | };`; 116 | 117 | module.exports = PostBuildHookWebpackPlugin; 118 | -------------------------------------------------------------------------------- /build/webpack/webpack.client.config.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | const common = require('./common'); 5 | const path = require('path'); 6 | const constants = require('../constants'); 7 | const configFileName = 'src/client/tsconfig.json'; 8 | const { DefinePlugin } = require('webpack'); 9 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 10 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); 11 | const PostBuildHookWebpackPlugin = require('./postBuildHook.js'); 12 | // Any build on the CI is considered production mode. 13 | const isProdBuild = constants.isCI || process.argv.some((argv) => argv.includes('mode') && argv.includes('production')); 14 | const filesToCopy = [ 15 | { 16 | from: path.join( 17 | constants.ExtensionRootDir, 18 | 'node_modules', 19 | '@vscode', 20 | 'jupyter-ipywidgets7', 21 | 'dist', 22 | 'ipywidgets.js' 23 | ), 24 | to: path.join( 25 | constants.ExtensionRootDir, 26 | 'out', 27 | 'node_modules', 28 | '@vscode', 29 | 'jupyter-ipywidgets7', 30 | 'dist', 31 | 'ipywidgets.js' 32 | ) 33 | }, 34 | { 35 | from: path.join( 36 | constants.ExtensionRootDir, 37 | 'node_modules', 38 | '@vscode', 39 | 'jupyter-ipywidgets8', 40 | 'dist', 41 | 'ipywidgets.js' 42 | ), 43 | to: path.join( 44 | constants.ExtensionRootDir, 45 | 'out', 46 | 'node_modules', 47 | '@vscode', 48 | 'jupyter-ipywidgets8', 49 | 'dist', 50 | 'ipywidgets.js' 51 | ) 52 | } 53 | ]; 54 | 55 | const defaultConfig = { 56 | context: constants.ExtensionRootDir, 57 | entry: { 58 | renderers: './src/client/index.tsx', 59 | markdown: './src/client/markdown.ts', 60 | builtinRendererHooks: './src/client/builtinRendererHooks.ts', 61 | vegaRenderer: './src/client/vegaRenderer.ts' 62 | }, 63 | output: { 64 | path: path.join(constants.ExtensionRootDir, 'out', 'client_renderer'), 65 | filename: '[name].js', 66 | chunkFilename: `[name].bundle.js`, 67 | libraryTarget: 'module' 68 | }, 69 | experiments: { 70 | outputModule: true 71 | }, 72 | mode: isProdBuild ? 'production' : 'development', 73 | devtool: isProdBuild ? 'source-map' : 'inline-source-map', 74 | externals: ['vscode', 'commonjs'], 75 | plugins: [ 76 | new ForkTsCheckerWebpackPlugin({ 77 | typescript: { 78 | diagnosticOptions: { 79 | syntactic: true 80 | }, 81 | configFile: configFileName, 82 | memoryLimit: 9096 83 | // reportFiles: ['src/client/**/*.{ts,tsx}'], 84 | } 85 | }), 86 | new DefinePlugin({ 87 | scriptUrl: 'import.meta.url', 88 | 'process.env': '{}' // utils references `process.env.xxx` 89 | }), 90 | new CopyWebpackPlugin({ 91 | patterns: [...filesToCopy] 92 | }), 93 | ...common.getDefaultPlugins('extension') 94 | ], 95 | stats: { 96 | performance: false 97 | }, 98 | performance: { 99 | hints: false 100 | }, 101 | resolve: { 102 | fallback: { 103 | fs: false, 104 | path: require.resolve('path-browserify'), 105 | 'ansi-to-react': path.join(__dirname, 'ansi-to-react.js'), 106 | util: require.resolve('util') // vega uses `util.promisify` (we need something that works outside node) 107 | }, 108 | extensions: ['.ts', '.tsx', '.js', '.json', '.svg'] 109 | }, 110 | module: { 111 | rules: [ 112 | { 113 | test: /\.tsx?$/, 114 | use: [ 115 | { 116 | loader: 'thread-loader', 117 | options: { 118 | // there should be 1 cpu for the fork-ts-checker-webpack-plugin 119 | workers: require('os').cpus().length - 1, 120 | workerNodeArgs: ['--max-old-space-size=9096'], 121 | poolTimeout: isProdBuild ? 1000 : Infinity // set this to Infinity in watch mode - see https://github.com/webpack-contrib/thread-loader 122 | } 123 | }, 124 | { 125 | loader: 'ts-loader', 126 | options: { 127 | happyPackMode: true, // IMPORTANT! use happyPackMode mode to speed-up compilation and reduce errors reported to webpack 128 | configFile: configFileName, 129 | // Faster (turn on only on CI, for dev we don't need this). 130 | transpileOnly: true, 131 | reportFiles: ['src/client/**/*.{ts,tsx}'] 132 | } 133 | } 134 | ] 135 | }, 136 | { 137 | test: /\.svg$/, 138 | use: ['svg-inline-loader'] 139 | }, 140 | { 141 | test: /\.css$/, 142 | use: ['style-loader', 'css-loader'] 143 | }, 144 | { 145 | test: /\.js$/, 146 | include: /node_modules.*remark.*default.*js/, 147 | use: [ 148 | { 149 | loader: path.resolve('./build/webpack/loaders/remarkLoader.js'), 150 | options: {} 151 | } 152 | ] 153 | }, 154 | { 155 | test: /\.json$/, 156 | type: 'javascript/auto', 157 | include: /node_modules.*remark.*/, 158 | use: [ 159 | { 160 | loader: path.resolve('./build/webpack/loaders/jsonloader.js'), 161 | options: {} 162 | } 163 | ] 164 | }, 165 | { 166 | test: /\.(png|woff|woff2|eot|gif|ttf)$/, 167 | use: [ 168 | { 169 | loader: 'url-loader?limit=100000', 170 | options: { esModule: false } 171 | } 172 | ] 173 | }, 174 | { 175 | test: /\.less$/, 176 | use: ['style-loader', 'css-loader', 'less-loader'] 177 | }, 178 | { 179 | test: /\.node$/, 180 | use: [ 181 | { 182 | loader: 'node-loader' 183 | } 184 | ] 185 | } 186 | ] 187 | } 188 | }; 189 | const preloadConfig = { 190 | context: constants.ExtensionRootDir, 191 | entry: { 192 | preload: './src/client/preload.ts' 193 | }, 194 | output: { 195 | path: path.join(constants.ExtensionRootDir, 'out', 'client_renderer'), 196 | filename: '[name].js', 197 | chunkFilename: `[name].bundle.js`, 198 | libraryTarget: 'module' 199 | }, 200 | experiments: { 201 | outputModule: true 202 | }, 203 | mode: isProdBuild ? 'production' : 'development', 204 | devtool: isProdBuild ? 'source-map' : 'inline-source-map', 205 | plugins: [ 206 | new ForkTsCheckerWebpackPlugin({ 207 | typescript: { 208 | diagnosticOptions: { 209 | syntactic: true 210 | }, 211 | configFile: configFileName, 212 | memoryLimit: 9096 213 | // reportFiles: ['src/client/**/*.{ts,tsx}'], 214 | } 215 | }), 216 | new DefinePlugin({ 217 | scriptUrl: 'import.meta.url', 218 | 'process.env': '{}' // utils references `process.env.xxx` 219 | }), 220 | ...common.getDefaultPlugins('extension'), 221 | new PostBuildHookWebpackPlugin() 222 | ], 223 | stats: { 224 | performance: false 225 | }, 226 | performance: { 227 | hints: false 228 | }, 229 | resolve: { 230 | fallback: { 231 | fs: false, 232 | path: require.resolve('path-browserify'), 233 | util: require.resolve('util') // vega uses `util.promisify` (we need something that works outside node) 234 | }, 235 | extensions: ['.ts', '.js'] 236 | } 237 | }; 238 | module.exports = [defaultConfig, preloadConfig]; 239 | -------------------------------------------------------------------------------- /build/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | module.exports = [require('./webpack.extension.config'), require('./webpack.client.config')]; 5 | -------------------------------------------------------------------------------- /build/webpack/webpack.extension.config.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | const path = require('path'); 5 | const constants = require('../constants'); 6 | const common = require('./common'); 7 | const configFileName = 'src/extension/tsconfig.json'; 8 | 9 | module.exports = { 10 | context: constants.ExtensionRootDir, 11 | target: 'node', 12 | entry: { 13 | extension: './src/extension/index.ts' 14 | }, 15 | output: { 16 | filename: 'index.js', 17 | path: path.resolve(constants.ExtensionRootDir, 'out', 'extension_renderer'), 18 | libraryTarget: 'commonjs2', 19 | devtoolModuleFilenameTemplate: '../../[resource-path]' 20 | }, 21 | mode: 'production', 22 | devtool: 'source-map', 23 | externals: ['vscode', 'commonjs'], 24 | plugins: [...common.getDefaultPlugins('extension')], 25 | resolve: { 26 | fallback: { 27 | 'ansi-to-react': path.join(__dirname, 'ansi-to-react.js') 28 | }, 29 | extensions: ['.ts', '.js'] 30 | }, 31 | node: { 32 | __dirname: false 33 | }, 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.ts$/, 38 | exclude: /node_modules/, 39 | use: [ 40 | { 41 | loader: 'ts-loader', 42 | options: { 43 | configFile: configFileName, 44 | reportFiles: ['src/extension/**/*.{ts,tsx}'] 45 | } 46 | } 47 | ] 48 | } 49 | ] 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-notebook-renderers/868651f80e8f87c690a7fcf3a860e9479e7912f2/icon.png -------------------------------------------------------------------------------- /images/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-notebook-renderers/868651f80e8f87c690a7fcf3a860e9479e7912f2/images/sample.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jupyter-renderers", 3 | "displayName": "Jupyter Notebook Renderers", 4 | "description": "Renderers for Jupyter Notebooks (with plotly, vega, gif, png, svg, jpeg and other such outputs)", 5 | "version": "1.2.0", 6 | "engines": { 7 | "vscode": "^1.95.0" 8 | }, 9 | "publisher": "ms-toolsai", 10 | "author": { 11 | "name": "Microsoft Corporation" 12 | }, 13 | "license": "MIT", 14 | "homepage": "https://github.com/Microsoft/vscode-notebook-renderers", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/Microsoft/vscode-notebook-renderers" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/Microsoft/vscode-notebook-renderers/issues" 21 | }, 22 | "qna": "https://stackoverflow.com/questions/tagged/visual-studio-code+jupyter", 23 | "icon": "icon.png", 24 | "galleryBanner": { 25 | "color": "#ffffff", 26 | "theme": "light" 27 | }, 28 | "keywords": [ 29 | "jupyter", 30 | "notebook", 31 | "notebookRenderer", 32 | "multi-root ready" 33 | ], 34 | "categories": [ 35 | "Other", 36 | "Data Science", 37 | "Machine Learning", 38 | "Notebooks", 39 | "Visualization" 40 | ], 41 | "activationEvents": [], 42 | "enabledApiProposals": [ 43 | "contribNotebookStaticPreloads" 44 | ], 45 | "main": "./out/extension_renderer/index.js", 46 | "browser": "./out/extension_renderer/index.js", 47 | "contributes": { 48 | "notebookPreload": [ 49 | { 50 | "type": "jupyter-notebook", 51 | "entrypoint": "./out/node_modules/@vscode/jupyter-ipywidgets7/dist/ipywidgets.js" 52 | }, 53 | { 54 | "type": "interactive", 55 | "entrypoint": "./out/node_modules/@vscode/jupyter-ipywidgets7/dist/ipywidgets.js" 56 | }, 57 | { 58 | "type": "jupyter-notebook", 59 | "entrypoint": "./out/node_modules/@vscode/jupyter-ipywidgets8/dist/ipywidgets.js" 60 | }, 61 | { 62 | "type": "interactive", 63 | "entrypoint": "./out/node_modules/@vscode/jupyter-ipywidgets8/dist/ipywidgets.js" 64 | }, 65 | { 66 | "type": "jupyter-notebook", 67 | "entrypoint": "./out/client_renderer/preload.js" 68 | }, 69 | { 70 | "type": "interactive", 71 | "entrypoint": "./out/client_renderer/preload.js" 72 | } 73 | ], 74 | "notebookRenderer": [ 75 | { 76 | "id": "jupyter-notebook-renderer", 77 | "entrypoint": "./out/client_renderer/renderers.js", 78 | "displayName": "Jupyter Notebook Renderer", 79 | "requiresMessaging": "optional", 80 | "mimeTypes": [ 81 | "image/gif", 82 | "image/png", 83 | "image/jpeg", 84 | "image/webp", 85 | "image/svg+xml", 86 | "application/geo+json", 87 | "application/vdom.v1+json", 88 | "application/vnd.dataresource+json", 89 | "application/vnd.plotly.v1+json", 90 | "application/vnd.vega.v2+json", 91 | "application/vnd.vega.v3+json", 92 | "application/vnd.vega.v4+json", 93 | "application/vnd.vegalite.v1+json", 94 | "application/vnd.vegalite.v2+json", 95 | "application/x-nteract-model-debug+json", 96 | "text/vnd.plotly.v1+html" 97 | ] 98 | }, 99 | { 100 | "id": "jupyter-notebook-built-in-renderer-hooks", 101 | "displayName": "Jupyter Notebook Html/JavaScript Renderer", 102 | "requiresMessaging": "optional", 103 | "entrypoint": { 104 | "extends": "vscode.builtin-renderer", 105 | "path": "./out/client_renderer/builtinRendererHooks.js" 106 | } 107 | }, 108 | { 109 | "id": "jupyter-vega-renderer", 110 | "displayName": "Jupyter Vega Renderer", 111 | "requiresMessaging": "optional", 112 | "entrypoint": "./out/client_renderer/vegaRenderer.js", 113 | "mimeTypes": [ 114 | "application/vnd.vega.v5+json", 115 | "application/vnd.vegalite.v3+json", 116 | "application/vnd.vegalite.v4+json", 117 | "application/vnd.vegalite.v5+json" 118 | ] 119 | }, 120 | { 121 | "id": "jupyter-markdown", 122 | "displayName": "Jupyter Markdown styles", 123 | "entrypoint": { 124 | "extends": "vscode.markdown-it-renderer", 125 | "path": "./out/client_renderer/markdown.js" 126 | } 127 | } 128 | ] 129 | }, 130 | "scripts": { 131 | "vscode:prepublish": "npm run compile:webpack", 132 | "dev": "concurrently -r npm:compile:extension:watch npm:compile:client:watch", 133 | "compile": "npm run compile:extension && npm run compile:client", 134 | "compile:extension": "tsc -p src/extension", 135 | "compile:extension:watch": "tsc -p src/extension --watch", 136 | "compile:webpack": "npm run build:client && npm run build:extension", 137 | "compile:client": "webpack --config=build/webpack/webpack.client.config.js", 138 | "build:client": "webpack --config=build/webpack/webpack.client.config.js --mode=production", 139 | "build:extension": "webpack --config=build/webpack/webpack.extension.config.js --mode=production", 140 | "compile:client:watch": "webpack --config=build/webpack/webpack.client.config.js --watch", 141 | "lint": "eslint src --ext ts && eslint src --ext tsx && eslint build --ext js", 142 | "lint-format": "npx prettier 'src/**/*.ts*' --check && npx prettier 'build/**/*.js' --check", 143 | "watch": "tsc -watch -p ./", 144 | "package": "vsce package -o ms-notebook-renderers.vsix", 145 | "prettier-fix": "prettier 'src/**/*.ts*' --write && prettier 'build/**/*.js' --write", 146 | "postinstall": "node ./build/postInstall.js" 147 | }, 148 | "dependencies": { 149 | "@babel/helper-validator-identifier": "^7.22.20", 150 | "@jupyterlab/nbformat": "^4.2.4", 151 | "@loadable/component": "^5.16.4", 152 | "@nteract/transform-dataresource": "^3.0.2", 153 | "@nteract/transform-geojson": "^3.2.3", 154 | "@nteract/transform-model-debug": "^3.2.3", 155 | "@nteract/transform-plotly": "^7.0.0", 156 | "@nteract/transform-vega": "7.0.10", 157 | "@nteract/transforms": "^3.2.0", 158 | "@vscode/jupyter-ipywidgets7": "^2.0.4", 159 | "@vscode/jupyter-ipywidgets8": "^1.0.8", 160 | "canvas": "^2.11.2", 161 | "chownr": "^2.0.0", 162 | "fs-minipass": "^2.1.0", 163 | "jquery": "^3.6.1", 164 | "lodash": "^4.17.21", 165 | "minipass": "^3.1.6", 166 | "minizlib": "^2.1.2", 167 | "path-browserify": "^0.0.1", 168 | "plotly.js-dist": "^3.0.1", 169 | "re-resizable": "~6.5.5", 170 | "react": "^16.8.4", 171 | "react-dom": "^16.8.4", 172 | "react-toggle": "^4.1.2", 173 | "requirejs": "^2.3.7", 174 | "styled-components": "^6.1.13", 175 | "tslib": "^1.14.1", 176 | "underscore": "^1.13.1", 177 | "util": "^0.12.3", 178 | "uuid": "^3.3.2", 179 | "vega": "^5.33.0", 180 | "vega-embed": "^6.29.0", 181 | "vega-lite": "^5.23.0", 182 | "yallist": "^4.0.0" 183 | }, 184 | "devDependencies": { 185 | "@types/copy-webpack-plugin": "^10.1.0", 186 | "@types/glob": "^7.1.1", 187 | "@types/loadable__component": "^5.10.0", 188 | "@types/lodash": "^4.14.158", 189 | "@types/markdown-it": "^12.2.3", 190 | "@types/node": "^12.11.7", 191 | "@types/react": "^16.9.35", 192 | "@types/react-dom": "^16.9.8", 193 | "@types/uuid": "^8.0.0", 194 | "@types/vscode": "^1.60.0", 195 | "@types/vscode-notebook-renderer": "^1.60.0", 196 | "@typescript-eslint/eslint-plugin": "^7.18.0", 197 | "@typescript-eslint/parser": "^7.18.0", 198 | "concurrently": "^5.2.0", 199 | "copy-webpack-plugin": "^12.0.2", 200 | "css-loader": "^7.1.2", 201 | "eslint": "^8.57.0", 202 | "eslint-config-prettier": "^9.1.0", 203 | "eslint-plugin-prettier": "^5.2.1", 204 | "fork-ts-checker-webpack-plugin": "^9.0.2", 205 | "glob": "^7.1.6", 206 | "less": "^4.2.0", 207 | "node-loader": "^2.0.0", 208 | "prettier": "^3.3.3", 209 | "style-loader": "^4.0.0", 210 | "svg-inline-loader": "^0.8.2", 211 | "thread-loader": "^4.0.2", 212 | "ts-loader": "^9.5.1", 213 | "typescript": "^5.5.4", 214 | "url-loader": "^4.1.1", 215 | "webpack": "^5.95.0", 216 | "webpack-bundle-analyzer": "^4.10.2", 217 | "webpack-cli": "^5.1.4", 218 | "webpack-fix-default-import-plugin": "^1.0.3" 219 | }, 220 | "overrides": { 221 | "simple-get": "3.1.1", 222 | "node-fetch": "2.6.7", 223 | "prismjs": "1.30.0", 224 | "url-parse@<1.5.9": "1.5.9", 225 | "terser@<5.14.2": "5.14.2", 226 | "ua-parser-js": "0.7.24", 227 | "moment@<2.29.4": "2.29.4", 228 | "nanoid@<3.3.8": "3.3.8", 229 | "ws@<7.4.6": "7.5.10" 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/client/builtinRendererHooks.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | // This must be on top, do not change. Required by webpack. 5 | // eslint-disable-next-line no-unused-vars 6 | declare let __webpack_public_path__: string; 7 | declare const scriptUrl: string; 8 | const getPublicPath = () => { 9 | return new URL(scriptUrl.replace(/[^/]+$/, '')).toString(); 10 | }; 11 | 12 | // eslint-disable-next-line prefer-const, no-unused-vars, @typescript-eslint/no-unused-vars 13 | __webpack_public_path__ = getPublicPath(); 14 | 15 | import type { OutputItem, RendererContext } from 'vscode-notebook-renderer'; 16 | if (!('$' in globalThis) && !('jQuery' in globalThis)) { 17 | // Required by JS code. 18 | // eslint-disable-next-line @typescript-eslint/no-var-requires 19 | const jQuery = require('jquery'); 20 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 21 | (globalThis as any).$ = jQuery; 22 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 23 | (globalThis as any).jQuery = jQuery; 24 | } 25 | export async function activate(ctx: RendererContext) { 26 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 27 | const builtinRenderer = await ctx.getRenderer('vscode.builtin-renderer'); 28 | if (!builtinRenderer) { 29 | throw new Error('Could not find the built-in js renderer'); 30 | } 31 | try { 32 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 33 | (builtinRenderer.experimental_registerHtmlRenderingHook as any)({ 34 | async postRender(_outputItem: OutputItem, element: HTMLElement, _signal: AbortSignal): Promise { 35 | // Output container is expected to have the class `output_html` 36 | element.classList.add('output_html'); 37 | return; 38 | } 39 | }); 40 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 41 | (builtinRenderer.experimental_registerJavaScriptRenderingHook as any)({ 42 | async preEvaluate( 43 | outputItem: OutputItem, 44 | element: HTMLElement, 45 | script: string, 46 | _signal: AbortSignal 47 | ): Promise { 48 | if (ctx.postMessage) { 49 | ctx.postMessage({ type: 'from Renderer', payload: 'Hello World' }); 50 | } 51 | const metadata = 52 | outputItem.metadata && typeof outputItem.metadata === 'object' && 'metadata' in outputItem.metadata 53 | ? // eslint-disable-next-line @typescript-eslint/no-explicit-any 54 | (outputItem.metadata as any)['metadata'] 55 | : undefined; 56 | return ` 57 | (function(){ 58 | let gotToUserScript = false; 59 | try { 60 | // Required by JS code in Jupyter notebook renderers such as ipyvega. 61 | // We're not fully supporting ipyvega yet, but this ensures the scripts will not fall over and will work with minimal effort on our part. 62 | const context = { 63 | outputs: [{ 64 | metadata: ${JSON.stringify(metadata || {})}, data: {} 65 | }], 66 | }; 67 | // Required by JS code in Jupyter notebook renderers again, even scenepic (Microsoft Python widget) uses this. 68 | const ele = $(document.getElementById("${element.id}")); 69 | (function (element){ 70 | gotToUserScript = true; 71 | ${script} 72 | }).call(context, ele); 73 | } catch (ex) { 74 | console.error('VS Code Renderer failed to render output', ex); 75 | if (gotToUserScript) { 76 | throw ex; 77 | } else { 78 | // Something went wrong in our script that was generated by us. 79 | ${script} 80 | } 81 | } 82 | })();`; 83 | } 84 | }); 85 | } catch (ex) { 86 | throw new Error(`Failed to register JavaScript rendering hook: ${ex}`); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/client/clipboard.ts: -------------------------------------------------------------------------------- 1 | declare const ClipboardItem: { 2 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 3 | prototype: any; 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-unused-vars 5 | new (options: any): any; 6 | }; 7 | declare interface ClipboardItem { 8 | readonly types: ReadonlyArray; 9 | // eslint-disable-next-line no-unused-vars 10 | getType(type: string): Promise; 11 | } 12 | 13 | export async function writeImageToClipboard(blob: Blob) { 14 | const item = new ClipboardItem({ 'image/png': blob }); 15 | if (!('write' in navigator.clipboard)) { 16 | throw new Error('navigator.clipboard.write not supported'); 17 | } 18 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 19 | await (navigator.clipboard as any).write([item]); 20 | } 21 | -------------------------------------------------------------------------------- /src/client/constants.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | export const JupyterNotebookRenderer = 'jupyter-notebook-renderer'; 5 | export type OpenImageInPlotViewer = { 6 | type: 'openImageInPlotViewer'; 7 | outputId: string; 8 | mimeType: string; 9 | }; 10 | export type IsJupyterExtensionInstalled = { 11 | type: 'isJupyterExtensionInstalled'; 12 | response?: boolean; 13 | }; 14 | export type SaveImageAs = { 15 | type: 'saveImageAs'; 16 | outputId: string; 17 | mimeType: string; 18 | }; 19 | export declare const ClipboardItem: { 20 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 21 | prototype: any; 22 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-unused-vars 23 | new (options: any): any; 24 | }; 25 | export const noop = () => { 26 | // noop 27 | }; 28 | 29 | export function isDarkTheme() { 30 | try { 31 | return (document.body.dataset.vscodeThemeKind || '').toLowerCase().includes('dark'); 32 | } catch { 33 | return false; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/client/helpers.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import type * as nbformat from '@jupyterlab/nbformat'; 5 | 6 | export function concatMultilineString(str: nbformat.MultilineString, trim?: boolean): string { 7 | const nonLineFeedWhiteSpaceTrim = /(^[\t\f\v\r ]+|[\t\f\v\r ]+$)/g; // Local var so don't have to reset the lastIndex. 8 | if (Array.isArray(str)) { 9 | let result = ''; 10 | for (let i = 0; i < str.length; i += 1) { 11 | const s = str[i]; 12 | if (i < str.length - 1 && !s.endsWith('\n')) { 13 | result = result.concat(`${s}\n`); 14 | } else { 15 | result = result.concat(s); 16 | } 17 | } 18 | 19 | // Just trim whitespace. Leave \n in place 20 | return trim ? result.replace(nonLineFeedWhiteSpaceTrim, '') : result; 21 | } 22 | return trim ? str.toString().replace(nonLineFeedWhiteSpaceTrim, '') : str.toString(); 23 | } 24 | -------------------------------------------------------------------------------- /src/client/index.css: -------------------------------------------------------------------------------- 1 | .plotIconSvgPath { 2 | fill: #424242 !important; 3 | fill-rule: evenodd !important; 4 | clip-rule: evenodd !important; 5 | } 6 | .hidden { 7 | display: none !important; 8 | } 9 | .plotIcon { 10 | cursor: pointer; 11 | } 12 | .svgContainerStyle svg { 13 | background-color: white; 14 | } 15 | -------------------------------------------------------------------------------- /src/client/index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | // This must be on top, do not change. Required by webpack. 5 | // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars 6 | declare let __webpack_public_path__: string; 7 | declare const scriptUrl: string; 8 | const getPublicPath = () => { 9 | return new URL(scriptUrl.replace(/[^/]+$/, '')).toString(); 10 | }; 11 | 12 | // eslint-disable-next-line prefer-const, @typescript-eslint/no-unused-vars, no-unused-vars 13 | __webpack_public_path__ = getPublicPath(); 14 | // This must be on top, do not change. Required by webpack. 15 | import './index.css'; 16 | import type * as nbformat from '@jupyterlab/nbformat'; 17 | import * as React from 'react'; 18 | import * as ReactDOM from 'react-dom'; 19 | import { CellOutput } from './render'; 20 | import { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer'; 21 | 22 | export const activate: ActivationFunction = (ctx: RendererContext) => { 23 | return { 24 | renderOutputItem(outputItem: OutputItem, element: HTMLElement) { 25 | renderOutput(outputItem, element, ctx); 26 | } 27 | }; 28 | }; 29 | 30 | /** 31 | * Called from renderer to render output. 32 | * This will be exposed as a public method on window for renderer to render output. 33 | */ 34 | function renderOutput(outputItem: OutputItem, element: HTMLElement, ctx: RendererContext) { 35 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 36 | const mimeString = outputItem.mime || (outputItem as any).mimeType; 37 | try { 38 | if (!ctx.workspace.isTrusted) { 39 | return; 40 | } 41 | const output = convertVSCodeOutputToExecuteResultOrDisplayData(outputItem); 42 | 43 | ReactDOM.render( 44 | React.createElement(CellOutput, { mimeType: mimeString, output, ctx, outputId: outputItem.id }, null), 45 | element 46 | ); 47 | } catch (ex) { 48 | console.error(`Failed to render mime type ${mimeString}`, ex); 49 | } 50 | } 51 | 52 | function convertVSCodeOutputToExecuteResultOrDisplayData( 53 | outputItem: OutputItem 54 | ): nbformat.IExecuteResult | nbformat.IDisplayData { 55 | const isImage = 56 | outputItem.mime.toLowerCase().startsWith('image/') && !outputItem.mime.toLowerCase().includes('svg'); 57 | // We add a metadata item `__isJson` to tell us whether the data is of type JSON or not. 58 | const isJson = (outputItem.metadata as Record)?.__isJson === true; 59 | const value = isImage ? outputItem.blob() : isJson ? outputItem.json() : outputItem.text(); 60 | return { 61 | data: { 62 | [outputItem.mime]: value 63 | }, 64 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 65 | metadata: (outputItem.metadata as any) || {}, 66 | execution_count: null, 67 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 68 | output_type: (outputItem.metadata as any)?.outputType || 'execute_result' 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /src/client/markdown.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | const styleContent = ` 7 | .alert { 8 | width: auto; 9 | padding: 1em; 10 | margin-top: 1em; 11 | margin-bottom: 1em; 12 | border-style: solid; 13 | border-width: 1px; 14 | } 15 | .alert > *:last-child { 16 | margin-bottom: 0; 17 | } 18 | #preview > .alert:last-child { 19 | /* Prevent this being set to zero by the default notebook stylesheet */ 20 | padding-bottom: 1em; 21 | } 22 | 23 | .alert-success { 24 | background-color: rgb(200,230,201); 25 | color: rgb(27,94,32); 26 | } 27 | .alert-info { 28 | background-color: rgb(178,235,242); 29 | color: rgb(0,96,100); 30 | } 31 | .alert-warning { 32 | background-color: rgb(255,224,178); 33 | color: rgb(230,81,0); 34 | } 35 | .alert-danger { 36 | background-color: rgb(255,205,210); 37 | color: rgb(183,28,28); 38 | } 39 | `; 40 | 41 | export async function activate() { 42 | const style = document.createElement('style'); 43 | style.textContent = styleContent; 44 | const template = document.createElement('template'); 45 | template.classList.add('markdown-style'); 46 | template.content.appendChild(style); 47 | document.head.appendChild(template); 48 | } 49 | -------------------------------------------------------------------------------- /src/client/preload.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | /** 5 | * Function required by VS Code as entry point for loading pre-load scripts. 6 | * This script is loaded for all Jupyter Notebook and Interactive Window editors. 7 | * As part of webpack build, the contents of jQuery and require.js are injected into this file. 8 | */ 9 | export async function activate() { 10 | // 11 | } 12 | -------------------------------------------------------------------------------- /src/client/render.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License. 4 | 5 | import type * as nbformat from '@jupyterlab/nbformat'; 6 | import type { PartialJSONObject } from '@lumino/coreutils'; 7 | import * as React from 'react'; 8 | import type { RendererContext } from 'vscode-notebook-renderer'; 9 | import { concatMultilineString } from './helpers'; 10 | import { getTransform } from './transforms'; 11 | import { OpenImageInPlotViewer, SaveImageAs, IsJupyterExtensionInstalled, noop } from './constants'; 12 | import { writeImageToClipboard } from './clipboard'; 13 | 14 | (globalThis as any).__isJupyterInstalled = false; 15 | export interface ICellOutputProps { 16 | output: nbformat.IExecuteResult | nbformat.IDisplayData; 17 | mimeType: string; 18 | ctx: RendererContext; 19 | outputId: string; 20 | } 21 | 22 | export class CellOutput extends React.Component { 23 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 24 | private readonly saveAsIcon: React.RefObject; 25 | private readonly plotIcon: React.RefObject; 26 | private readonly copyImageIcon: React.RefObject; 27 | private readonly disposables: { 28 | dispose: () => void; 29 | }[] = []; 30 | constructor(prop: ICellOutputProps) { 31 | super(prop); 32 | this.saveAsIcon = React.createRef(); 33 | this.plotIcon = React.createRef(); 34 | this.copyImageIcon = React.createRef(); 35 | } 36 | public componentWillUnmount() { 37 | this.disposables.forEach((d) => d.dispose()); 38 | } 39 | public render() { 40 | const mimeBundle = this.props.output.data as nbformat.IMimeBundle; // NOSONAR 41 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 42 | let data = mimeBundle[this.props.mimeType!]; 43 | 44 | // For un-executed output we might get text or svg output as multiline string arrays 45 | // we want to concat those so we don't display a bunch of weird commas as we expect 46 | // Single strings in our output 47 | if (Array.isArray(data)) { 48 | data = concatMultilineString(data as nbformat.MultilineString, true); 49 | } 50 | 51 | switch (this.props.mimeType) { 52 | case 'image/svg+xml': 53 | case 'image/png': 54 | case 'image/gif': 55 | case 'image/jpeg': 56 | case 'image/webp': 57 | return this.renderImage( 58 | this.props.mimeType, 59 | data as unknown as Blob | MediaSource, 60 | this.props.output.metadata 61 | ); 62 | default: 63 | return this.renderOutput(data, this.props.mimeType); 64 | } 65 | } 66 | /** 67 | * Custom rendering of image/png and image/jpeg to handle custom Jupyter metadata. 68 | * Behavior adopted from Jupyter lab. 69 | * For mimetype image/svg+xml, the data type will be string. 70 | */ 71 | private renderImage(mimeType: string, data: Blob | MediaSource, metadata: Record = {}) { 72 | if (this.props.ctx.postMessage) { 73 | this.props.ctx.postMessage({ type: 'isJupyterExtensionInstalled' } as IsJupyterExtensionInstalled); 74 | } 75 | if (this.props.ctx.onDidReceiveMessage) { 76 | const disposable = this.props.ctx.onDidReceiveMessage((response: Partial) => { 77 | if (response?.type === 'isJupyterExtensionInstalled' && response.response) { 78 | (globalThis as any).__isJupyterInstalled = response.response; 79 | } 80 | }); 81 | this.disposables.push(disposable); 82 | } 83 | 84 | const imgStyle: Record = { maxWidth: '100%', height: 'auto' }; 85 | const divStyle: Record = { overflow: 'scroll', position: 'relative' }; // `overflow:scroll` is the default style used by Jupyter lab. 86 | const imgSrc = 87 | mimeType.toLowerCase().includes('svg') && typeof data === 'string' ? undefined : URL.createObjectURL(data); 88 | const customMetadata = metadata.metadata as PartialJSONObject | undefined; 89 | const showPlotViewer = metadata.__displayOpenPlotIcon === true; 90 | const showCopyImage = mimeType === 'image/png'; 91 | const copyButtonMargin = showPlotViewer ? '85px' : '45px'; 92 | if (customMetadata && typeof customMetadata.needs_background === 'string') { 93 | imgStyle.backgroundColor = customMetadata.needs_background === 'light' ? 'white' : 'black'; 94 | } 95 | const imgHeightWidth: { height?: number; width?: number } = {}; 96 | const imageMetadata: Record | undefined = customMetadata 97 | ? (customMetadata[mimeType] as any) 98 | : undefined; 99 | if (imageMetadata?.height) { 100 | imgHeightWidth.height = imageMetadata.height; 101 | } 102 | if (imageMetadata?.width) { 103 | imgHeightWidth.width = imageMetadata.width; 104 | } 105 | if (imageMetadata?.unconfined === true) { 106 | imgStyle.maxWidth = 'none'; 107 | } 108 | 109 | // Hack, use same classes as used in VSCode for images (keep things as similar as possible). 110 | // This is to maintain consistently in displaying images (if we hadn't used HTML). 111 | // See src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts 112 | const saveAs = () => { 113 | if (this.props.ctx.postMessage) { 114 | this.props.ctx.postMessage({ 115 | type: 'saveImageAs', 116 | outputId: this.props.outputId, 117 | mimeType: this.props.mimeType 118 | } as SaveImageAs); 119 | } 120 | }; 121 | const openPlot = () => { 122 | if (this.props.ctx.postMessage) { 123 | this.props.ctx.postMessage({ 124 | type: 'openImageInPlotViewer', 125 | outputId: this.props.outputId, 126 | mimeType: this.props.mimeType 127 | } as OpenImageInPlotViewer); 128 | } 129 | }; 130 | const copyPlotImage = () => { 131 | writeImageToClipboard(data as Blob).then(noop); 132 | }; 133 | const onMouseOver = () => { 134 | if (!(globalThis as any).__isJupyterInstalled) { 135 | return; 136 | } 137 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 138 | this.saveAsIcon.current!.className = 'plotIcon'; 139 | if (this.plotIcon.current) { 140 | this.plotIcon.current.className = 'plotIcon'; 141 | } 142 | if (this.copyImageIcon.current) { 143 | this.copyImageIcon.current.className = 'plotIcon'; 144 | } 145 | }; 146 | const onMouseOut = () => { 147 | if (!(globalThis as any).__isJupyterInstalled) { 148 | return; 149 | } 150 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 151 | this.saveAsIcon.current!.className = 'plotIcon hidden'; 152 | if (this.plotIcon.current) { 153 | this.plotIcon.current.className = 'plotIcon hidden'; 154 | } 155 | if (this.copyImageIcon.current) { 156 | this.copyImageIcon.current.className = 'plotIcon hidden'; 157 | } 158 | }; 159 | const contents = imgSrc ? ( 160 | 161 | ) : ( 162 |
163 | ); 164 | return ( 165 |
166 | 193 | {showPlotViewer ? ( 194 | 221 | ) : undefined} 222 | {showCopyImage ? ( 223 | 251 | ) : undefined} 252 | {contents} 253 |
254 | ); 255 | } 256 | private renderOutput(data: nbformat.MultilineString | PartialJSONObject, mimeType?: string) { 257 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unused-vars, no-unused-vars, @typescript-eslint/no-explicit-any 258 | const Transform: any = getTransform(this.props.mimeType!); 259 | const vegaPlot = mimeType && isVegaPlot(mimeType); 260 | const divStyle: React.CSSProperties = { 261 | backgroundColor: vegaPlot ? 'white' : undefined 262 | }; 263 | if (vegaPlot) { 264 | // Vega library expects data to be passed as serialized JSON instead of a native 265 | // JS object. 266 | data = typeof data === 'string' ? data : JSON.stringify(data); 267 | } 268 | return ( 269 |
270 | 271 |
272 | ); 273 | } 274 | } 275 | 276 | function isVegaPlot(mimeType: string) { 277 | return mimeType.includes('application/vnd.vega'); 278 | } 279 | -------------------------------------------------------------------------------- /src/client/transforms.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars 5 | import * as React from 'react'; 6 | import Loadable, { LoadableComponent } from '@loadable/component'; 7 | 8 | class TransformData { 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | private cachedPromise: undefined | Promise; 11 | constructor( 12 | // eslint-disable-next-line no-unused-vars 13 | public mimeType: string, 14 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-unused-vars 15 | private importer: () => Promise 16 | ) {} 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 18 | public getComponent(): Promise { 19 | if (!this.cachedPromise) { 20 | this.cachedPromise = this.importer(); 21 | } 22 | return this.cachedPromise; 23 | } 24 | } 25 | 26 | // Hardcode mimeType here so we can do a quick lookup without loading all of the 27 | // other components. 28 | const mimeTypeToImport: TransformData[] = [ 29 | new TransformData('application/vnd.vega.v2+json', async () => { 30 | const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); 31 | return module.Vega2; 32 | }), 33 | new TransformData('application/vnd.vega.v3+json', async () => { 34 | const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); 35 | return module.Vega3; 36 | }), 37 | new TransformData('application/vnd.vega.v4+json', async () => { 38 | const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); 39 | return module.Vega4; 40 | }), 41 | new TransformData('application/vnd.vega.v5+json', async () => { 42 | const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); 43 | return module.Vega5; 44 | }), 45 | new TransformData('application/vnd.vegalite.v1+json', async () => { 46 | const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); 47 | return module.VegaLite1; 48 | }), 49 | new TransformData('application/vnd.vegalite.v2+json', async () => { 50 | const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); 51 | return module.VegaLite2; 52 | }), 53 | new TransformData('application/vnd.vegalite.v3+json', async () => { 54 | const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); 55 | return module.VegaLite3; 56 | }), 57 | new TransformData('application/vnd.vegalite.v4+json', async () => { 58 | const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); 59 | return module.VegaLite4; 60 | }), 61 | new TransformData('application/geo+json', async () => { 62 | const module = await import(/* webpackChunkName: "geojson" */ '@nteract/transform-geojson'); 63 | return module.GeoJSONTransform; 64 | }), 65 | new TransformData('application/vnd.dataresource+json', async () => { 66 | const module = await import(/* webpackChunkName: "dataresource" */ '@nteract/transform-dataresource'); 67 | return module.DataResourceTransform; 68 | }), 69 | new TransformData('application/x-nteract-model-debug+json', async () => { 70 | const module = await import(/* webpackChunkName: "modeldebug" */ '@nteract/transform-model-debug'); 71 | return module.default; 72 | }), 73 | new TransformData('text/vnd.plotly.v1+html', async () => { 74 | const module = await import(/* webpackChunkName: "plotly" */ '@nteract/transform-plotly'); 75 | return module.PlotlyNullTransform; 76 | }), 77 | new TransformData('application/vnd.plotly.v1+json', async () => { 78 | const module = await import(/* webpackChunkName: "plotly" */ '@nteract/transform-plotly'); 79 | return module.PlotlyTransform; 80 | }), 81 | new TransformData('image/svg+xml', async () => { 82 | const module = await import(/* webpackChunkName: "nteract_transforms" */ '@nteract/transforms'); 83 | return module.SVGTransform; 84 | }), 85 | new TransformData('application/vdom.v1+json', async () => { 86 | const module = await import(/* webpackChunkName: "nteract_transforms_vsdom" */ '@nteract/transform-vdom'); 87 | return module.VDOM; 88 | }) 89 | ]; 90 | 91 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 92 | export function getRichestMimetype(data: any): string { 93 | // Go through the keys of this object and find their index in the map 94 | let index = mimeTypeToImport.length; 95 | const keys = Object.keys(data); 96 | keys.forEach((k) => { 97 | const keyIndex = mimeTypeToImport.findIndex((m) => m.mimeType === k); 98 | if (keyIndex >= 0 && keyIndex < index) { 99 | // If higher up the chain, pick the higher up key 100 | index = keyIndex; 101 | } 102 | }); 103 | 104 | // If this index is found, return the mimetype to use. 105 | if (index < mimeTypeToImport.length) { 106 | return mimeTypeToImport[index].mimeType; 107 | } 108 | 109 | // Don't know which to pick, just pick the first. 110 | return keys[0]; 111 | } 112 | 113 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 114 | export function getTransform(mimeType: string, loadingMessage = 'Loading...'): LoadableComponent<{ data: any }> { 115 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 116 | return Loadable<{ data: any }>( 117 | async () => { 118 | const match = mimeTypeToImport.find((m) => m.mimeType === mimeType); 119 | if (match) { 120 | const transform = await match.getComponent(); 121 | return transform; 122 | } 123 | 124 | return
`Transform not found for mimetype ${mimeType}`
; 125 | }, 126 | { fallback:
{loadingMessage}
} 127 | ); 128 | } 129 | 130 | export async function forceLoad() { 131 | // Used for tests to make sure we don't end up with 'Loading ...' anywhere in a test 132 | await Promise.all(mimeTypeToImport.map((m) => m.getComponent())); 133 | } 134 | 135 | export function isMimeTypeSupported(mimeType: string): boolean { 136 | const match = mimeTypeToImport.find((m) => m.mimeType === mimeType); 137 | return match ? true : false; 138 | } 139 | -------------------------------------------------------------------------------- /src/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | // noEmit prevents the default tsc from building this--we use webpack instead 5 | "noEmit": true, 6 | "rootDir": ".", 7 | "module": "esnext", 8 | "lib": [ 9 | "ES2019", 10 | "dom" 11 | ], 12 | "jsx": "react", 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/client/types/@nteract/transform-dataresource.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@nteract/transform-dataresource' { 2 | export = index; 3 | const index: any; 4 | } 5 | -------------------------------------------------------------------------------- /src/client/types/@nteract/transform-geojson.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@nteract/transform-geojson' { 2 | export = index; 3 | const index: any; 4 | } 5 | -------------------------------------------------------------------------------- /src/client/types/@nteract/transform-model-debug.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@nteract/transform-model-debug' { 2 | export default class _default { 3 | static MIMETYPE: string; 4 | constructor(...args: any[]); 5 | forceUpdate(callback: any): void; 6 | render(): any; 7 | setState(partialState: any, callback: any): void; 8 | shouldComponentUpdate(): any; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/client/types/@nteract/transform-plotly.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@nteract/transform-plotly' { 2 | export function PlotlyNullTransform(): any; 3 | export namespace PlotlyNullTransform { 4 | const MIMETYPE: string; 5 | } 6 | export class PlotlyTransform { 7 | static MIMETYPE: string; 8 | constructor(...args: any[]); 9 | componentDidMount(): void; 10 | componentDidUpdate(): void; 11 | forceUpdate(callback: any): void; 12 | render(): any; 13 | setState(partialState: any, callback: any): void; 14 | shouldComponentUpdate(nextProps: any): any; 15 | } 16 | export default class _default { 17 | static MIMETYPE: string; 18 | constructor(...args: any[]); 19 | componentDidMount(): void; 20 | componentDidUpdate(): void; 21 | forceUpdate(callback: any): void; 22 | render(): any; 23 | setState(partialState: any, callback: any): void; 24 | shouldComponentUpdate(nextProps: any): any; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/client/types/@nteract/transform-vsdom.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@nteract/transform-vdom' { 2 | export class VDOM { 3 | static MIMETYPE: string; 4 | constructor(...args: any[]); 5 | componentDidMount(): void; 6 | componentDidUpdate(): void; 7 | forceUpdate(callback: any): void; 8 | render(): any; 9 | setState(partialState: any, callback: any): void; 10 | shouldComponentUpdate(nextProps: any): any; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/client/types/@nteract/transforms.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@nteract/transforms' { 2 | export class GIFTransform { 3 | static MIMETYPE: string; 4 | constructor(...args: any[]); 5 | forceUpdate(callback: any): void; 6 | render(): any; 7 | setState(partialState: any, callback: any): void; 8 | } 9 | export class HTMLTransform { 10 | static MIMETYPE: string; 11 | constructor(...args: any[]); 12 | componentDidMount(): void; 13 | componentDidUpdate(): void; 14 | forceUpdate(callback: any): void; 15 | render(): any; 16 | setState(partialState: any, callback: any): void; 17 | shouldComponentUpdate(nextProps: any): any; 18 | } 19 | export class JPEGTransform { 20 | static MIMETYPE: string; 21 | constructor(...args: any[]); 22 | forceUpdate(callback: any): void; 23 | render(): any; 24 | setState(partialState: any, callback: any): void; 25 | } 26 | export class JSONTransform { 27 | static MIMETYPE: string; 28 | static defaultProps: { 29 | data: {}; 30 | metadata: {}; 31 | theme: string; 32 | }; 33 | static handles(mimetype: any): any; 34 | constructor(props: any); 35 | forceUpdate(callback: any): void; 36 | render(): any; 37 | setState(partialState: any, callback: any): void; 38 | shouldComponentUpdate(nextProps: any): any; 39 | shouldExpandNode(): any; 40 | } 41 | export class JavaScriptTransform { 42 | static MIMETYPE: string; 43 | static handles(mimetype: any): any; 44 | constructor(...args: any[]); 45 | componentDidMount(): void; 46 | componentDidUpdate(): void; 47 | forceUpdate(callback: any): void; 48 | render(): any; 49 | setState(partialState: any, callback: any): void; 50 | shouldComponentUpdate(nextProps: any): any; 51 | } 52 | export function LaTeXTransform(props: any, context: any): any; 53 | export namespace LaTeXTransform { 54 | const MIMETYPE: string; 55 | namespace contextTypes { 56 | function MathJax(p0: any, p1: any, p2: any, p3: any, p4: any, p5: any): any; 57 | namespace MathJax { 58 | function isRequired(p0: any, p1: any, p2: any, p3: any, p4: any, p5: any): any; 59 | } 60 | function MathJaxContext(p0: any, p1: any, p2: any, p3: any, p4: any, p5: any): any; 61 | namespace MathJaxContext { 62 | function isRequired(p0: any, p1: any, p2: any, p3: any, p4: any, p5: any): any; 63 | } 64 | } 65 | } 66 | export class MarkdownTransform { 67 | static MIMETYPE: string; 68 | constructor(...args: any[]); 69 | forceUpdate(callback: any): void; 70 | render(): any; 71 | setState(partialState: any, callback: any): void; 72 | shouldComponentUpdate(nextProps: any): any; 73 | } 74 | export class PNGTransform { 75 | static MIMETYPE: string; 76 | constructor(...args: any[]); 77 | forceUpdate(callback: any): void; 78 | render(): any; 79 | setState(partialState: any, callback: any): void; 80 | } 81 | export class SVGTransform { 82 | static MIMETYPE: string; 83 | constructor(...args: any[]); 84 | componentDidMount(): void; 85 | componentDidUpdate(): void; 86 | forceUpdate(callback: any): void; 87 | render(): any; 88 | setState(partialState: any, callback: any): void; 89 | shouldComponentUpdate(nextProps: any): any; 90 | } 91 | export class TextTransform { 92 | static MIMETYPE: string; 93 | constructor(...args: any[]); 94 | forceUpdate(callback: any): void; 95 | render(): any; 96 | setState(partialState: any, callback: any): void; 97 | shouldComponentUpdate(nextProps: any): any; 98 | } 99 | export const displayOrder: string[]; 100 | export function registerTransform(_ref: any, transform: any): any; 101 | export function richestMimetype(bundle: any, ...args: any[]): any; 102 | export const standardDisplayOrder: string[]; 103 | 104 | export let standardTransforms: {}; 105 | export namespace transforms {} 106 | } 107 | -------------------------------------------------------------------------------- /src/client/vegaRenderer.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // This must be on top, do not change. Required by webpack. 5 | // eslint-disable-next-line no-unused-vars 6 | declare let __webpack_public_path__: string; 7 | declare const scriptUrl: string; 8 | const getPublicPath = () => { 9 | return new URL(scriptUrl.replace(/[^/]+$/, '')).toString(); 10 | }; 11 | 12 | // eslint-disable-next-line prefer-const, @typescript-eslint/no-unused-vars, no-unused-vars 13 | __webpack_public_path__ = getPublicPath(); 14 | 15 | import * as vegaEmbed from 'vega-embed'; 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | import { isDarkTheme } from './constants'; 18 | import { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer'; 19 | 20 | const vegaViews = new Map(); 21 | const VEGA_MIME_TYPE = 'application/vnd.vega.v5+json'; 22 | export const activate: ActivationFunction = (_ctx: RendererContext) => { 23 | return { 24 | async renderOutputItem(outputItem: OutputItem, element: HTMLElement) { 25 | const metadata: Record = 26 | outputItem.metadata && typeof outputItem.metadata === 'object' && 'metadata' in outputItem.metadata 27 | ? // eslint-disable-next-line @typescript-eslint/no-explicit-any 28 | (outputItem.metadata as any)['metadata'] 29 | : {}; 30 | const mode: vegaEmbed.Mode = outputItem.mime === VEGA_MIME_TYPE ? 'vega' : 'vega-lite'; 31 | const spec: vegaEmbed.VisualizationSpec = outputItem.json(); 32 | if (spec === undefined) { 33 | return; 34 | } 35 | const mimeMetadata = metadata && outputItem.mime in metadata ? metadata[outputItem.mime] || {} : {}; 36 | const embedOptions: vegaEmbed.EmbedOptions = 37 | typeof mimeMetadata === 'object' && mimeMetadata ? mimeMetadata.embed_options || {} : {}; 38 | 39 | if (isDarkTheme() && !embedOptions.theme && !spec.background) { 40 | // Set the theme as dark only if user has not already defined a them or background color for the chart. 41 | // I.e. we don't want to override user settings. 42 | embedOptions.theme = 'dark'; 43 | } 44 | // Dispose of any existing view. 45 | vegaViews.get(outputItem.id)?.finalize(); 46 | const loader = vegaEmbed.vega.loader({ 47 | http: { credentials: 'same-origin' } 48 | }); 49 | const sanitize = async (uri: string, options: unknown) => { 50 | // Use the resolver for any URIs it wants to handle 51 | const resolver = this._resolver; 52 | if (resolver?.isLocal && resolver.isLocal(uri)) { 53 | const absPath = await resolver.resolveUrl(uri); 54 | uri = await resolver.getDownloadUrl(absPath); 55 | } 56 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 57 | return loader.sanitize(uri, options as any); 58 | }; 59 | // Don't use `element`, else the menu options appear on the far right. 60 | // Because `element` has a fixed width, and `ele` (newly created) does not. 61 | const ele = document.createElement('div'); 62 | element.appendChild(ele); 63 | const result = await vegaEmbed.default(ele, spec, { 64 | actions: { 65 | export: true, 66 | compiled: false, // No point displaying these menu options if they do not work. 67 | editor: false, // No point displaying these menu options if they do not work. 68 | source: false // No point displaying these menu options if they do not work. 69 | }, 70 | defaultStyle: true, 71 | ...embedOptions, 72 | mode, 73 | loader: { ...loader, sanitize } 74 | }); 75 | vegaViews.set(outputItem.id, result); 76 | // Store generated image in output, send this to the extension. 77 | // Add png representation of vega chart to output 78 | // const imageURL = await this._result.view.toImageURL('png'); 79 | // model.setData({ 80 | // data: { ...model.data, 'image/png': imageURL.split(',')[1] } 81 | // }); 82 | }, 83 | disposeOutputItem(id?) { 84 | if (id) { 85 | vegaViews.get(id)?.finalize(); 86 | } 87 | } 88 | }; 89 | }; 90 | -------------------------------------------------------------------------------- /src/extension/constants.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as path from 'path'; 5 | export const EXTENSION_ROOT_DIR = path.join(__dirname, '..', '..'); 6 | export type OpenImageInPlotViewer = { 7 | type: 'openImageInPlotViewer'; 8 | outputId: string; 9 | mimeType: string; 10 | }; 11 | export type IsJupyterExtensionInstalled = { 12 | type: 'isJupyterExtensionInstalled'; 13 | response?: boolean; 14 | }; 15 | export type SaveImageAs = { 16 | type: 'saveImageAs'; 17 | outputId: string; 18 | mimeType: string; 19 | }; 20 | -------------------------------------------------------------------------------- /src/extension/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { Event, EventEmitter, ExtensionContext, notebooks, extensions, NotebookEditor } from 'vscode'; 5 | import { OpenImageInPlotViewer, SaveImageAs, IsJupyterExtensionInstalled } from './constants'; 6 | 7 | export async function activate(context: ExtensionContext): Promise<{ 8 | onDidReceiveMessage: Event<{ editor: NotebookEditor; message: OpenImageInPlotViewer | SaveImageAs }>; 9 | }> { 10 | const onDidReceiveMessage = new EventEmitter<{ 11 | editor: NotebookEditor; 12 | message: OpenImageInPlotViewer | SaveImageAs; 13 | }>(); 14 | const messaging = notebooks.createRendererMessaging('jupyter-notebook-renderer'); 15 | context.subscriptions.push( 16 | messaging.onDidReceiveMessage(({ editor, message }) => { 17 | const msg = message as OpenImageInPlotViewer | SaveImageAs | IsJupyterExtensionInstalled; 18 | if (!msg.type) { 19 | return; 20 | } 21 | if (msg.type === 'isJupyterExtensionInstalled') { 22 | void messaging 23 | .postMessage( 24 | { 25 | type: 'isJupyterExtensionInstalled', 26 | response: !!extensions.getExtension('ms-toolsai.jupyter') 27 | }, 28 | editor 29 | ) 30 | .then( 31 | () => { 32 | /* noop */ 33 | }, 34 | (ex) => console.error('Failed to send', ex) 35 | ); 36 | return; 37 | } 38 | onDidReceiveMessage.fire({ editor, message: msg }); 39 | }) 40 | ); 41 | messaging.postMessage({ 42 | type: 'isJupyterExtensionInstalled', 43 | response: !!extensions.getExtension('ms-toolsai.jupyter') 44 | }); 45 | 46 | return { 47 | onDidReceiveMessage: onDidReceiveMessage.event 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /src/extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "../../out/extension_renderer", 6 | "lib": [ 7 | "ES2019", 8 | "dom" 9 | ], 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/tsconfig-base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2019", 5 | "lib": [ 6 | "ES2019" 7 | ], 8 | "moduleResolution": "node", 9 | "sourceMap": true, 10 | "experimentalDecorators": true, 11 | "allowSyntheticDefaultImports": true, 12 | "noImplicitThis": false, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": false, 15 | "strict": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./src/client" 6 | }, 7 | { 8 | "path": "./src/extension" 9 | }, 10 | { 11 | "path": "./src/test" 12 | } 13 | ] 14 | } 15 | --------------------------------------------------------------------------------