├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ └── workflow.yaml ├── .gitignore ├── .nvmrc ├── .prettierrc.js ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TODO.md ├── build ├── ci │ ├── ci.yaml │ ├── pr.yaml │ └── templates │ │ ├── globals.yml │ │ ├── jobs │ │ └── build_compile.yml │ │ └── steps │ │ ├── build.yml │ │ ├── compile.yml │ │ ├── dependencies.yml │ │ ├── initialization.yml │ │ └── test.yml ├── constants.js ├── fixNodeModules.js └── webpack │ ├── common.js │ ├── loaders │ ├── jsonloader.js │ └── remarkLoader.js │ ├── plugins │ └── less-plugin-base64.js │ ├── webpack.client.config.js │ ├── webpack.config.js │ └── webpack.extension.config.js ├── images ├── REPL.gif └── demo.gif ├── package-lock.json ├── package.json ├── resources ├── docs │ ├── .vscode │ │ └── settings.json │ ├── arquero │ │ └── htmlOutput.nnb │ ├── basics │ │ ├── debug.md │ │ ├── debug.nnb │ │ ├── debugCell.gif │ │ ├── debugToolbar.gif │ │ ├── interrupt.gif │ │ ├── interrupt.md │ │ ├── richOutput.md │ │ ├── richOutput.nnb │ │ ├── sample.md │ │ ├── sample.png │ │ ├── samples.md │ │ ├── shellScripts.nnb │ │ └── tips.nnb │ ├── danfojs │ │ ├── embedInDiv.md │ │ ├── embedInDiv.nnb │ │ ├── embedInDiv.png │ │ ├── htmlOutput.md │ │ ├── htmlOutput.nnb │ │ ├── htmlOutput.png │ │ ├── htmlOutputSeries.png │ │ ├── plots.md │ │ ├── plots.nnb │ │ └── plots.png │ ├── plotly │ │ ├── embedInDiv.md │ │ ├── embedInDiv.nnb │ │ ├── embedInDiv.png │ │ ├── generate.md │ │ ├── generate.nnb │ │ ├── generate.png │ │ ├── saveToFile.md │ │ ├── saveToFile.nnb │ │ └── saveToFile.png │ └── tensorflow │ │ ├── carPrice.nnb │ │ ├── mnist.nnb │ │ ├── mnist.png │ │ ├── modelSummary.png │ │ ├── sample.md │ │ ├── sample.nnb │ │ ├── scatterPlot.png │ │ ├── scatterPlot2.png │ │ ├── tensorboard.md │ │ ├── tensorboard.nnb │ │ ├── tensorboard.png │ │ ├── tfvis.png │ │ └── visualizations.nnb ├── icons │ ├── logo.png │ ├── tf.png │ └── tf.svg └── scripts │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── shell.js │ ├── tfjs │ ├── tf.min.js │ └── tfjs-vis.umd.min.js │ └── tslib.js ├── src ├── client │ ├── common.ts │ ├── index.css │ ├── index.ts │ ├── plotGenerator.ts │ └── tfvis.ts ├── extension │ ├── configuration.ts │ ├── const.ts │ ├── content │ │ ├── index.ts │ │ ├── provider.ts │ │ └── walkThrough.ts │ ├── coreUtils.ts │ ├── index.ts │ ├── kernel │ │ ├── asyncWrapper.ts │ │ ├── browserExecution.ts │ │ ├── cellExecutionQueue.ts │ │ ├── cellOutput.ts │ │ ├── compiler.ts │ │ ├── controller.ts │ │ ├── debugger │ │ │ ├── commands.ts │ │ │ ├── debugFactory.ts │ │ │ └── debugger.ts │ │ ├── executionOrder.ts │ │ ├── index.ts │ │ ├── jsKernel.ts │ │ ├── plotly.ts │ │ ├── problems.ts │ │ ├── repl.ts │ │ ├── shellKernel.ts │ │ └── types.ts │ ├── serializer.ts │ ├── server │ │ ├── codeExecution.ts │ │ ├── comms.ts │ │ ├── extensions │ │ │ ├── arqueroFormatter.ts │ │ │ ├── danfoFormatter.ts │ │ │ ├── danforPlotter.ts │ │ │ ├── format.ts │ │ │ ├── heatmap.ts │ │ │ ├── plotly.ts │ │ │ ├── readLineProxy.ts │ │ │ ├── tensorFormatter.ts │ │ │ └── tfjsVisProxy.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── magics │ │ │ ├── base.ts │ │ │ └── variables.ts │ │ ├── tsnode.ts │ │ └── types.ts │ ├── serverLogger.ts │ ├── tfjsvis │ │ └── index.ts │ ├── types.ts │ ├── utils.ts │ └── vscode.d.ts ├── node-kernel │ ├── README.md │ ├── index.d.ts │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── plotly.js │ │ ├── LICENSE │ │ ├── index.d.ts │ │ └── lib │ │ ├── core.d.ts │ │ └── traces │ │ ├── box.d.ts │ │ ├── candlestick.d.ts │ │ ├── ohcl.d.ts │ │ ├── pie.d.ts │ │ ├── scatter.d.ts │ │ └── violin.d.ts ├── test │ ├── runTest.ts │ ├── suite │ │ ├── TESTS.md │ │ ├── compiler.test.ts │ │ ├── execution.test.ts │ │ └── index.ts │ └── vscode.d.ts └── tsconfig-base.json ├── tsconfig.client.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.js: -------------------------------------------------------------------------------- 1 | module.exports = { 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 | settings: { 13 | 'import/resolver': { 14 | node: { 15 | extensions: ['.js', '.jsx', '.ts', '.tsx'] 16 | } 17 | } 18 | }, 19 | plugins: [ 20 | 'eslint-plugin-import', 21 | 'eslint-plugin-jsdoc', 22 | 'eslint-plugin-no-null', 23 | 'eslint-plugin-prefer-arrow', 24 | '@typescript-eslint', 25 | 'prettier' 26 | ], 27 | extends: [ 28 | 'eslint:recommended', 29 | 'plugin:@typescript-eslint/eslint-recommended', 30 | 'plugin:@typescript-eslint/recommended', 31 | 'prettier' 32 | ], 33 | rules: { 34 | 'import/no-unresolved': 'off', 35 | // Overriding ESLint rules with Typescript-specific ones 36 | '@typescript-eslint/ban-ts-comment': [ 37 | 'error', 38 | { 39 | 'ts-ignore': 'allow-with-description' 40 | } 41 | ], 42 | '@typescript-eslint/explicit-module-boundary-types': 'off', 43 | 'no-bitwise': 'off', 44 | 'no-dupe-class-members': 'off', 45 | '@typescript-eslint/no-dupe-class-members': 'error', 46 | 'no-empty-function': 'off', 47 | '@typescript-eslint/no-empty-function': ['error'], 48 | '@typescript-eslint/no-empty-interface': 'off', 49 | '@typescript-eslint/no-explicit-any': 'error', 50 | '@typescript-eslint/no-non-null-assertion': 'off', 51 | 'no-unused-vars': 'off', 52 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '_\\w*' }], 53 | 'no-use-before-define': 'off', 54 | '@typescript-eslint/no-use-before-define': [ 55 | 'error', 56 | { 57 | functions: false 58 | } 59 | ], 60 | 'no-useless-constructor': 'off', 61 | '@typescript-eslint/no-useless-constructor': 'error', 62 | '@typescript-eslint/no-var-requires': 'off', 63 | '@typescript-eslint/no-floating-promises': 'error', 64 | 65 | // Other rules 66 | 'class-methods-use-this': 'off', 67 | 'func-names': 'off', 68 | 'import/extensions': 'off', 69 | 'import/namespace': 'off', 70 | 'import/no-extraneous-dependencies': 'off', 71 | 'import/no-unresolved': [ 72 | 'error', 73 | { 74 | ignore: ['monaco-editor', 'vscode'] 75 | } 76 | ], 77 | 'import/prefer-default-export': 'off', 78 | 'linebreak-style': 'off', 79 | 'no-await-in-loop': 'off', 80 | 'no-console': 'off', 81 | 'no-control-regex': 'off', 82 | 'no-extend-native': 'off', 83 | 'no-multi-str': 'off', 84 | 'no-param-reassign': 'off', 85 | 'no-prototype-builtins': 'off', 86 | 'no-restricted-syntax': [ 87 | 'error', 88 | { 89 | selector: 'ForInStatement', 90 | message: 91 | 'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.' 92 | }, 93 | 94 | { 95 | selector: 'LabeledStatement', 96 | message: 97 | 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.' 98 | }, 99 | { 100 | selector: 'WithStatement', 101 | message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.' 102 | } 103 | ], 104 | 'no-template-curly-in-string': 'off', 105 | 'no-underscore-dangle': 'off', 106 | 'no-useless-escape': 'off', 107 | 'no-void': [ 108 | 'error', 109 | { 110 | allowAsStatement: true 111 | } 112 | ], 113 | 'operator-assignment': 'off', 114 | strict: 'off' 115 | }, 116 | overrides: [ 117 | { 118 | files: ['**/*.test.ts', '**/*.test.tsx'], 119 | env: { 120 | mocha: true 121 | } 122 | }, 123 | { 124 | files: ['**/src/client/**/*.ts*'], 125 | parserOptions: { 126 | project: 'src/client/tsconfig.json', 127 | sourceType: 'module' 128 | }, 129 | env: { 130 | browser: true 131 | }, 132 | rules: { 133 | 'import/no-unresolved': 'off' 134 | } 135 | }, 136 | { 137 | files: ['**/src/extension/**/*.ts*'], 138 | parserOptions: { 139 | project: 'src/extension/tsconfig.json', 140 | sourceType: 'module' 141 | }, 142 | env: { 143 | browser: true 144 | }, 145 | rules: { 146 | 'import/no-unresolved': 'off' 147 | } 148 | }, 149 | { 150 | files: ['build/**/*.js'], 151 | rules: { 152 | '@typescript-eslint/no-var-requires': 'off' 153 | } 154 | }, 155 | { 156 | files: ['build/**/plugins/**/*.js'], 157 | rules: { 158 | 'no-unused-vars': 'off', 159 | '@typescript-eslint/no-unused-vars': 'off', 160 | '@typescript-eslint/no-empty-function': 'off' 161 | } 162 | }, 163 | { 164 | files: ['src/**/*.d.ts'], 165 | rules: { 166 | '@typescript-eslint/no-explicit-any': 'off', 167 | '@typescript-eslint/ban-types': 'off', 168 | '@typescript-eslint/adjacent-overload-signatures': 'off', 169 | 'no-irregular-whitespace': 'off' 170 | } 171 | } 172 | ] 173 | }; 174 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yaml: -------------------------------------------------------------------------------- 1 | # Disable as GitHub actions is super flaky (npm ci fails, building webpack fails). 2 | 3 | # name: CI 4 | 5 | # on: 6 | # push: 7 | # branches: 8 | # - release 9 | # - master 10 | # paths-ignore: # dont run when changes made to these folders 11 | # - '.vscode/**' 12 | # pull_request: 13 | 14 | # env: 15 | # CACHE_NPM_DEPS: cache-npm 16 | # CACHE_OUT_DIRECTORY: cache-out-directory 17 | 18 | # jobs: 19 | # js-ts-deps: 20 | # name: Install npm dependencies 21 | # runs-on: ubuntu-latest 22 | # steps: 23 | # - name: Checkout 24 | # uses: actions/checkout@v2 25 | 26 | # - name: Install dependencies (npm ci) 27 | # run: npm ci --prefer-offline 28 | 29 | # - name: Cache npm files 30 | # uses: actions/cache@v1 31 | # with: 32 | # path: ~/.npm 33 | # key: ${{runner.os}}-${{env.CACHE_NPM_DEPS}}-${{hashFiles('package-lock.json')}} 34 | 35 | # compile-hygiene-test: 36 | # name: Compile, lint, check for errors 37 | # runs-on: ubuntu-latest 38 | # needs: [js-ts-deps] 39 | # steps: 40 | # - name: Checkout 41 | # uses: actions/checkout@v2 42 | 43 | # - name: Retrieve cached npm files 44 | # uses: actions/cache@v1 45 | # with: 46 | # path: ~/.npm 47 | # key: ${{runner.os}}-${{env.CACHE_NPM_DEPS}}-${{hashFiles('package-lock.json')}} 48 | 49 | # - name: Install dependencies (npm ci) 50 | # run: npm ci --prefer-offline 51 | 52 | # - name: Compile 53 | # run: npm run compile 54 | 55 | # - name: Run linting on TypeScript code 56 | # run: npm run lint 57 | 58 | # - name: Run prettier on TypeScript code 59 | # run: npx prettier 'src/**/*.ts*' --check 60 | 61 | # - name: Run tests 62 | # env: 63 | # DISPLAY: 10 64 | # uses: GabrielBB/xvfb-action@v1.0 65 | # with: 66 | # run: npm run test 67 | 68 | # build-vsix: 69 | # name: Build VSIX 70 | # runs-on: ubuntu-latest 71 | # needs: [js-ts-deps, compile-hygiene-test] 72 | # steps: 73 | # - name: Checkout 74 | # uses: actions/checkout@v2 75 | 76 | # - name: Retrieve cached npm files 77 | # uses: actions/cache@v1 78 | # with: 79 | # path: ~/.npm 80 | # key: ${{runner.os}}-${{env.CACHE_NPM_DEPS}}-${{hashFiles('package-lock.json')}} 81 | 82 | # - name: Install dependencies (npm ci) 83 | # run: npm ci --prefer-offline 84 | 85 | # - name: Install VSC 86 | # run: npm i vsce 87 | 88 | # - name: Package the VSIX 89 | # run: npm run package 90 | 91 | # - uses: actions/upload-artifact@v1 92 | # with: 93 | # name: ms-notebook-renderers.vsix 94 | # path: ms-notebook-renderers.vsix 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | out 3 | temp 4 | node_modules 5 | .vscode-test/ 6 | *.vsix 7 | **/*.analyzer.html 8 | **/*.stats.json 9 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v14 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 | { 10 | "name": "Run Extension", 11 | "type": "extensionHost", 12 | "request": "launch", 13 | "runtimeExecutable": "${execPath}", 14 | "args": [ 15 | "--extensionDevelopmentPath=${workspaceFolder}" 16 | ], 17 | "outFiles": [ 18 | "${workspaceFolder}/out/**/*.js" 19 | ] 20 | }, 21 | { 22 | "type": "pwa-node", 23 | "request": "attach", 24 | "name": "Attach to JS Kernel", 25 | "address": "localhost", 26 | "protocol": "inspector", 27 | "port": 40653, 28 | "sourceMaps": true, 29 | "skipFiles": [ 30 | "/**" 31 | ], 32 | "outFiles": ["${workspaceRoot}/out/**/*.js"] 33 | }, 34 | { 35 | "name": "Extension Tests", 36 | "type": "extensionHost", 37 | "request": "launch", 38 | "runtimeExecutable": "${execPath}", 39 | "args": [ 40 | "--enable-proposed-api", 41 | "--extensionDevelopmentPath=${workspaceFolder}", 42 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 43 | ], 44 | "outFiles": [ 45 | "${workspaceFolder}/out/test/**/*.js" 46 | ], 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /.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.tabWidth": 4, 28 | "prettier.trailingComma": "none", 29 | "prettier.printWidth": 120, 30 | "editor.codeActionsOnSave": { 31 | // "source.fixAll.eslint": true 32 | }, 33 | "typescript.preferences.importModuleSpecifier": "relative", 34 | "debug.javascript.usePreview": true 35 | } 36 | -------------------------------------------------------------------------------- /.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 | out/**/*.stats.json 7 | out/**/*..analyzer.html 8 | src/** 9 | resources/scripts/tfjs/** 10 | temp/** 11 | .gitignore 12 | !resources/scripts/node_modules/** 13 | node_modules/** 14 | vsc-extension-quickstart.md 15 | **/*.js.map 16 | **/tsconfig.json 17 | **/tsconfig-base.json 18 | **/.gitignore 19 | **/.editorconfig 20 | **/.eslintrc.json 21 | **/.prettierrc.js 22 | **/*.ts 23 | **/*.stats.json 24 | **/*.analyzer.html 25 | **/*.bundle.js.map 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.5 (8 Nov 2021) 4 | * Ensure it works in WSL (https://github.com/DonJayamanne/typescript-notebook/issues/44) 5 | 6 | ## 2.0.4 (12 Oct 2021) 7 | * Support for a REPL (Interactive Window) experience (https://github.com/DonJayamanne/typescript-notebook/issues/37) 8 | * Fix typescript compilation (https://github.com/DonJayamanne/typescript-notebook/issues/40) 9 | * Updates to TypeScript 4.4.4 10 | 11 | ## 2.0.3 (12 Sept 2021) 12 | * Support for user input in notebooks using [readline](https://nodejs.org/api/readline.html#readline_readline_createinterface_options) 13 | * Update samples to use `isomorphic-fetch` instead of `node-fetch` (and pre-requisite `npm` packages). 14 | 15 | ## 2.0.2 (6 Sept 2021) 16 | * Excellent support for [arquero](https://uwdata.github.io/arquero/) (rich HTML output) 17 | * New notebooks default cells to `typescript` 18 | * Displaying cell execution errors on Linux 19 | * Display `Tensorflow Visualization` panel only when required. 20 | * Ability to always hide `Tensorflow Visualization` panel via the setting `"node_notebook.disableTensorflowVis": true` 21 | 22 | ## 2.0.1 (2 Sept 2021) 23 | * Updated readme 24 | 25 | ## 2.0.0 (2 Sept 2021) 26 | * Run & debug JavaScript, TypeScript code in node.js 27 | * Built in support for typescript (ships with [TypeScript](https://www.typescriptlang.org/) & [ts-node](https://typestrong.org/ts-node/)). 28 | * Built in support for [plotly](https://plotly.com/javascript/) (plotly.js is shipped with the extension) 29 | * Rich (inline visualizations) using [@tensorflow/tfjs-vis](https://www.npmjs.com/package/@tensorflow/tfjs-vis) & [Tensorboards](https://www.tensorflow.org/tensorboard) 30 | * Excellent support for [danfo.js](https://danfo.jsdata.org/) (rich HTML output and plots) 31 | * Run shell scripts within the notebook cell. 32 | * Quickly prototype and view HTML/JavaScript/CSS output 33 | 34 | -------------------------------------------------------------------------------- /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/) 14 9 | 1. npm 6.13.4 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/DonJayamanne/typescript-notebook 21 | cd typescript-notebook 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 vsce 47 | > npm ci 48 | > npm run package 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Don Jayamanne 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 | # Node.js Notebooks 2 | ## Features 3 | * Enhanced REPL experience for Node.js in Notebooks (with top level awaits) 4 | * Run & debug JavaScript, TypeScript code in node.js 5 | * Built in support for typescript (ships with [TypeScript](https://www.typescriptlang.org/) & [ts-node](https://typestrong.org/ts-node/)). 6 | * Built in support for [plotly](https://plotly.com/javascript/) (plotly.js is shipped with the extension) 7 | * Rich (inline visualizations) using [@tensorflow/tfjs-vis](https://www.npmjs.com/package/@tensorflow/tfjs-vis) & [Tensorboards](https://www.tensorflow.org/tensorboard) 8 | * Excellent support for [danfo.js](https://danfo.jsdata.org/) (rich HTML output and plots) 9 | * Excellent support for [arquero](https://uwdata.github.io/arquero/) (rich HTML output) 10 | * Run shell scripts within the notebook cell. 11 | * Quickly prototype and view HTML/JavaScript/CSS output 12 | * Support for user input using [readline](https://nodejs.org/api/readline.html#readline_readline_createinterface_options) 13 | 14 | 15 | Packages such [plotly](https://plotly.com/javascript/), [tfjs-vis](https://www.npmjs.com/package/@tensorflow/tfjs-vis) & [danfo.js](https://danfo.jsdata.org/) support rich visualization only in the browser, 16 | however, this extension leverages the power of Notebooks to provide the same rich visualizations when targeting node.js. 17 | 18 | Use the command `Open a sample node.js notebook` to open a sample notebook to get started with plotly.js, danfo.js, tensorflow.js, etc. 19 | 20 | ## Getting started 21 | * For a REPL experience use the command `Open Node.js REPL` 22 | * Consider installing the [Jupyter](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter) extension for an enhance user interface (toolbars). 23 | * For a notebook experience, create a file with the extension `*.nnb`, e.g. `sample.nnb` 24 | * Or use the menu item `New File...` to create a Node.js notebook 25 | 26 | 27 | ![Repl](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/images/REPL.gif) 28 | ![Demo](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/images/demo.gif) 29 | 30 | 31 | ## Examples 32 | * Use the command `Open a sample node.js notebook` to open a sample notebook. 33 | * Use the command `Welcome: Open Walkthrough...` to checkout the samples. 34 | 35 | ## Requirements 36 | * node.js >= 12 37 | * node.js needs to be in the current path 38 | 39 | ## Roadmap 40 | * Open a plain js/ts file as a notebook & vice versa. 41 | * Better renderers for tabular data (arquero, danfo.js, etc) 42 | * [Vega](https://vega.github.io/vega/) plots without having to install vega 43 | * Custom node arguments 44 | 45 | 46 | ### Known issues, workarounds and technical details 47 | * See [here](https://github.com/DonJayamanne/typescript-notebook/wiki/Kernel-behaviour-(known-issues-&-workarounds)) for more details 48 | 49 | 50 | ## Thanks 51 | Thanks to the various packages we provide integrations with which help make this extension useful: 52 | * [ts-node](https://typestrong.org/ts-node/) 53 | * [Tensorflow.js](https://www.tensorflow.org/js) 54 | * [plotly](https://plotly.com/javascript/) 55 | * [danfo.js](https://danfo.jsdata.org/) 56 | * [node-pty](https://github.com/microsoft/node-pty) 57 | * [arquero](https://uwdata.github.io/arquero/) 58 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 1. Use tsconfig from users workspace folder, closest to the ipynb file. 2 | 3. Interactive widow will be a great addition 3 | 4. Better way to view image tensors (helper to generate images & probably zoom in - or just use Jupyter's tensor visualizer) 4 | 5. Variable viewer? or tensor viewer or easy image viewer (from tensors)? 5 | 6. Changing cwd permanently in shells? (probably not) 6 | 7. Cell magics (to change CWD, send variable from node to browser)? 7 | 8. Use shell path from vscode settings 8 | Ignore language (doesn't matter whether its shell or powershell). 9 | At the end of the day the shell is setup by the user. 10 | 11. tfjs-vis, Use a webview instead of a Panel, as the state is not preserved & its not tied to a particular notebook. 11 | This way we can open the new webview on the side. 12 | 12. Use ESBuild for extension (excluding `node-pty`, that's better bundled & shipped in node_modules folder, this way the bundle will pick it) 13 | 13. Fix prettier, etc 14 | 14. Links in error output 15 | 15. Tests 16 | # Bugs 17 | * Enable `supportBreakingOnExceptionsInDebugger` 18 | For break point exceptions 19 | * Fix stack traces in exceptions 20 | 21 | * Following code fails 22 | ``` 23 | console.log(typeof some) 24 | var {x}={x:'xyz1234'},y,some = ' ',([a,b]=[1,2]); 25 | doIt(); 26 | ``` 27 | 28 | * When debugging, we do'nt see variables 29 | * Should wrap every cell in an IIFE, so we can see variables as variables. 30 | * Display message if we fail to start node process (currently just hangs) 31 | * Magics must be excluded from being executed as JS code (else parser will fall over & hang) 32 | 33 | # Telemetry 34 | * See whether we need variable hoisting 35 | (easy, parse the code & check if we have classes/functions) 36 | * Variable hoisting 37 | * At worst, we notify users that this will not work when debugging (yuck) 38 | * Or we open a dummy cell & start debugging that code (yuck) 39 | 40 | 41 | 42 | # Known issues 43 | * Hoisting is always an issue (after all its just hacky, we're changing user code) 44 | * Printing value of last expression vs `console.log` 45 | See below 46 | ```typescript 47 | var s = await Promise.resolve(1); 48 | function bye(){ 49 | console.log("Bye"); 50 | } 51 | bye(); 52 | s 53 | ``` 54 | When you run this, the value `1` will be displayed first and then we'll see `Bye`. 55 | This is because we get output from repl before we get output from stdout of the process. 56 | ** SOLUTION ** 57 | * After we get the result from the repl, we can send a `console.log()`, this will 58 | tell the UI that we have some output that will be coming. 59 | Next we sent out result via websockets. We should not display the output we got from the socket 60 | until we've received the `` from `process.stdout`, once we wait, we know any `console.log` the 61 | user sent would have been received & printed in the right order. 62 | Thus if we look at the above example, the output would be in the right order as follows: 63 | ``` 64 | Bye 65 | 1 66 | ``` 67 | 68 | -------------------------------------------------------------------------------- /build/ci/ci.yaml: -------------------------------------------------------------------------------- 1 | # CI build (PR merge) 2 | 3 | name: '$(Year:yyyy).$(Month).0.$(BuildID)-ci' 4 | 5 | # Notes: Only trigger a commit for main and release, and skip build/rebuild 6 | # on changes in the news and .vscode folders. 7 | trigger: 8 | branches: 9 | include: ['main', 'release*'] 10 | paths: 11 | exclude: ['/.vscode'] 12 | 13 | # Not the PR build for merges to main and release. 14 | pr: none 15 | 16 | # Variables that are available for the entire pipeline. 17 | variables: 18 | - template: templates/globals.yml 19 | 20 | stages: 21 | - stage: Build 22 | jobs: 23 | - template: templates/jobs/build_compile.yml 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /build/ci/templates/globals.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | NodeVersion: '12.15.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-16.04' 8 | steps: 9 | - template: ../steps/compile.yml 10 | 11 | - job: Build 12 | pool: 13 | vmImage: 'ubuntu-16.04' 14 | steps: 15 | - template: ../steps/build.yml 16 | 17 | - job: Test 18 | pool: 19 | vmImage: 'ubuntu-16.04' 20 | steps: 21 | - template: ../steps/test.yml 22 | 23 | - job: Dependencies 24 | pool: 25 | vmImage: 'ubuntu-16.04' 26 | steps: 27 | - template: ../steps/dependencies.yml 28 | 29 | - job: Hygiene 30 | pool: 31 | vmImage: 'ubuntu-16.04' 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 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 | const path = require('path'); 2 | exports.ExtensionRootDir = path.dirname(__dirname); 3 | exports.isWindows = /^win/.test(process.platform); 4 | exports.isCI = process.env.TF_BUILD !== undefined; 5 | -------------------------------------------------------------------------------- /build/fixNodeModules.js: -------------------------------------------------------------------------------- 1 | // backgroundColor: 'white' 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const { ExtensionRootDir } = require('./constants'); 5 | 6 | function fixTensorflowVisColors() { 7 | const files = [ 8 | path.join(ExtensionRootDir, 'node_modules', '@tensorflow', 'tfjs-vis', 'dist', 'components', 'visor.js'), 9 | path.join(ExtensionRootDir, 'node_modules', '@tensorflow', 'tfjs-vis', 'dist', 'components', 'tabs.js'), 10 | path.join(ExtensionRootDir, 'node_modules', '@tensorflow', 'tfjs-vis', 'dist', 'components', 'surface.js') 11 | ]; 12 | 13 | files.forEach((file) => { 14 | let contents = fs.readFileSync(file).toString(); 15 | if (contents.indexOf("backgroundColor: 'white',") || contents.indexOf("backgroundColor: '#fafafa',")) { 16 | contents = contents.replace("backgroundColor: 'white',", ''); 17 | contents = contents.replace("backgroundColor: 'white',", ''); 18 | contents = contents.replace("backgroundColor: '#fafafa',", ''); 19 | contents = contents.replace("backgroundColor: '#fafafa',", ''); 20 | fs.writeFileSync(file, contents, { flag: 'w' }); 21 | } 22 | }); 23 | } 24 | 25 | fixTensorflowVisColors(); 26 | -------------------------------------------------------------------------------- /build/webpack/common.js: -------------------------------------------------------------------------------- 1 | const webpack_bundle_analyzer = require('webpack-bundle-analyzer'); 2 | function getDefaultPlugins(name) { 3 | return [ 4 | new webpack_bundle_analyzer.BundleAnalyzerPlugin({ 5 | analyzerMode: 'static', 6 | reportFilename: `${name}.analyzer.html`, 7 | generateStatsFile: true, 8 | statsFilename: `${name}.stats.json`, 9 | openAnalyzer: false 10 | }) 11 | ]; 12 | } 13 | exports.getDefaultPlugins = getDefaultPlugins; 14 | -------------------------------------------------------------------------------- /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/webpack.client.config.js: -------------------------------------------------------------------------------- 1 | const common = require('./common'); 2 | const FixDefaultImportPlugin = require('webpack-fix-default-import-plugin'); 3 | const path = require('path'); 4 | const constants = require('../constants'); 5 | const configFileName = 'tsconfig.client.json'; 6 | const CopyPlugin = require("copy-webpack-plugin"); 7 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); 8 | // Any build on the CI is considered production mode. 9 | const isProdBuild = constants.isCI || process.argv.some((argv) => argv.includes('mode') && argv.includes('production')); 10 | 11 | module.exports = { 12 | context: constants.ExtensionRootDir, 13 | entry: { 14 | tfjsvis: './src/client/index.ts', 15 | tfjsvisRenderer: './src/client/tfvis.ts', 16 | plotGenerator: './src/client/plotGenerator.ts' 17 | }, 18 | output: { 19 | path: path.join(constants.ExtensionRootDir, 'out', 'views'), 20 | filename: '[name].js', 21 | chunkFilename: `[name].bundle.js`, 22 | libraryTarget: 'module', 23 | library: { type: 'module' }, 24 | environment: { module: true } 25 | }, 26 | experiments: { 27 | outputModule: true 28 | }, 29 | mode: isProdBuild ? 'production' : 'development', 30 | devtool: isProdBuild ? 'source-map' : 'inline-source-map', 31 | externals: ['@tensorflow/tfjs'], 32 | // externals: ['@tensorflow/tfjs-vis', '@tensorflow/tfjs'], 33 | plugins: [ 34 | ...common.getDefaultPlugins('client'), 35 | // new FixDefaultImportPlugin(), 36 | new ForkTsCheckerWebpackPlugin({ 37 | typescript: { 38 | checkSyntacticErrors: true, 39 | configFile: configFileName, 40 | memoryLimit: 9096 41 | } 42 | 43 | // reportFiles: ['src/client/**/*.{ts,tsx}'] 44 | }), 45 | // ...common.getDefaultPlugins('extension') 46 | new CopyPlugin({ 47 | patterns: [ 48 | { from: path.join(constants.ExtensionRootDir, "node_modules/typescript/lib/typescript.js"), to: path.join(constants.ExtensionRootDir, "resources/scripts/node_modules/typescript/index.js") }, 49 | { from: path.join(constants.ExtensionRootDir, "node_modules/typescript/LICENSE.txt"), to: path.join(constants.ExtensionRootDir, "resources/scripts/node_modules/typescript/LICENSE.txt") } 50 | ], 51 | }), 52 | ], 53 | stats: { 54 | performance: false 55 | }, 56 | performance: { 57 | hints: false 58 | }, 59 | resolve: { 60 | extensions: ['.ts', '.tsx', '.js', '.json', '.svg'] 61 | }, 62 | module: { 63 | rules: [ 64 | { 65 | test: /\.js$/, 66 | include: /node_modules.*remark.*default.*js/, 67 | use: [ 68 | 'ify-loader', 69 | 'transform-loader?plotly.js/tasks/compress_attributes.js', 70 | { 71 | loader: path.resolve('./build/webpack/loaders/remarkLoader.js'), 72 | options: {} 73 | } 74 | ] 75 | }, 76 | { 77 | test: /\.tsx?$/, 78 | use: [ 79 | { loader: 'cache-loader' }, 80 | { 81 | loader: 'thread-loader', 82 | options: { 83 | // there should be 1 cpu for the fork-ts-checker-webpack-plugin 84 | workers: require('os').cpus().length - 1, 85 | workerNodeArgs: ['--max-old-space-size=9096'], 86 | poolTimeout: isProdBuild ? 1000 : Infinity // set this to Infinity in watch mode - see https://github.com/webpack-contrib/thread-loader 87 | } 88 | }, 89 | { 90 | loader: 'ts-loader', 91 | options: { 92 | happyPackMode: true, // IMPORTANT! use happyPackMode mode to speed-up compilation and reduce errors reported to webpack 93 | configFile: configFileName, 94 | // Faster (turn on only on CI, for dev we don't need this). 95 | transpileOnly: true, 96 | reportFiles: ['src/client/**/*.{ts,tsx}'], 97 | ignoreDiagnostics: ['TS7006'] 98 | } 99 | } 100 | ] 101 | }, 102 | { 103 | test: /\.svg$/, 104 | use: ['svg-inline-loader'] 105 | }, 106 | { 107 | test: /\.css$/, 108 | use: ['style-loader', 'css-loader'] 109 | }, 110 | { 111 | test: /\.json$/, 112 | type: 'javascript/auto', 113 | include: /node_modules.*remark.*/, 114 | use: [ 115 | { 116 | loader: path.resolve('./build/webpack/loaders/jsonloader.js'), 117 | options: {} 118 | } 119 | ] 120 | }, 121 | { 122 | test: /\.(png|woff|woff2|eot|gif|ttf)$/, 123 | use: [ 124 | { 125 | loader: 'url-loader?limit=100000', 126 | options: { esModule: false } 127 | } 128 | ] 129 | }, 130 | { 131 | test: /\.less$/, 132 | use: ['style-loader', 'css-loader', 'less-loader'] 133 | } 134 | ] 135 | } 136 | }; 137 | -------------------------------------------------------------------------------- /build/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | require('./webpack.extension.config'), 3 | require('./webpack.client.config') 4 | ]; 5 | -------------------------------------------------------------------------------- /build/webpack/webpack.extension.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const constants = require('../constants'); 3 | const common = require('./common'); 4 | const configFileName = 'tsconfig.json'; 5 | 6 | module.exports = { 7 | context: constants.ExtensionRootDir, 8 | target: 'node', 9 | entry: { 10 | extension: './src/extension/index.ts', 11 | server: './src/extension/server/index.ts', 12 | test: './src/test/runTest.ts' 13 | }, 14 | output: { 15 | filename: (pathData) => { 16 | if (pathData.chunk.name === 'server') { 17 | return path.join('out', 'extension', 'server', 'index.js'); 18 | } else if (pathData.chunk.name === 'test') { 19 | return path.join('out', 'test', 'runTest.js'); 20 | } else if (pathData.chunk.name === 'typescript') { 21 | return path.join('resources', 'scripts', 'node_modules', 'typescript', 'index.js'); 22 | } 23 | return path.join('out', 'extension', 'index.js'); 24 | }, 25 | path: path.resolve(constants.ExtensionRootDir), 26 | libraryTarget: 'commonjs2', 27 | devtoolModuleFilenameTemplate: '../../[resource-path]' 28 | }, 29 | mode: 'production', 30 | devtool: 'source-map', 31 | externals: [ 32 | 'vscode', 33 | 'commonjs', 34 | 'bufferutil', 35 | 'utf-8-validate', 36 | 'node-pty', 37 | 'profoundjs-node-pty', 38 | 'xterm', 39 | 'xterm-addon-serialize', 40 | 'node-kernel' 41 | ], 42 | plugins: [...common.getDefaultPlugins('extension')], 43 | resolve: { 44 | extensions: ['.ts', '.js'] 45 | }, 46 | node: { 47 | __dirname: false 48 | }, 49 | module: { 50 | rules: [ 51 | { 52 | test: /\.ts$/, 53 | exclude: /node_modules/, 54 | use: [ 55 | { 56 | loader: 'ts-loader', 57 | options: { 58 | configFile: configFileName, 59 | reportFiles: ['src/extension/**/*.{ts,tsx}'] 60 | } 61 | } 62 | ] 63 | } 64 | ] 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /images/REPL.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/images/REPL.gif -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/images/demo.gif -------------------------------------------------------------------------------- /resources/docs/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/Users/donjayamanne/miniforge3/envs/tf/bin/python" 3 | } 4 | -------------------------------------------------------------------------------- /resources/docs/arquero/htmlOutput.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "javascript", 5 | "source": [ 6 | "const { loadArrow } = require('arquero');\nconst flights = await loadArrow('https://vega.github.io/vega-datasets/data/flights-200k.arrow');\nflights" 7 | ], 8 | "outputs": [] 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /resources/docs/basics/debug.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | Debug javascript and typescript without installing typescript. 4 | Add a breakpoint and then either, 5 | * Click on the `Debug Cell` dropdown menu next to the `Run` icon. 6 | * Or click on the `Debug Notebook` icon found on the top right (Editor toolbar). 7 | 8 | **Option 1** 9 | ![Option1](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/basics/debugCell.gif) 10 | 11 | **Option 2** 12 | ![Option2](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/basics/debugToolbar.gif) 13 | -------------------------------------------------------------------------------- /resources/docs/basics/debug.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "javascript", 5 | "source": "function add(a, b) {\n return a + b;\n}" 6 | }, 7 | { 8 | "language": "typescript", 9 | "source": "const result = add(1, 2);\nconsole.log(result)" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /resources/docs/basics/debugCell.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/basics/debugCell.gif -------------------------------------------------------------------------------- /resources/docs/basics/debugToolbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/basics/debugToolbar.gif -------------------------------------------------------------------------------- /resources/docs/basics/interrupt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/basics/interrupt.gif -------------------------------------------------------------------------------- /resources/docs/basics/interrupt.md: -------------------------------------------------------------------------------- 1 | # Interrupting and clearing the state 2 | 3 | If you have a long running piece of code you'd like to stop, then you can click on the stop icon next to the cell. You can optionally click on the `restart` icon on the top right of the editor toolbar. 4 | 5 | Note: 6 | * Restarting and interrupting is the same. All state information is lost (the associated node.js process is killed). 7 | * Closing a notebook, automatically kills the associated process. 8 | 9 | ![Interrupting](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/basics/interrupt.gif) 10 | -------------------------------------------------------------------------------- /resources/docs/basics/richOutput.md: -------------------------------------------------------------------------------- 1 | # Rich outputs 2 | 3 | `node-kernel` is built into the notebook runtime. You do not need to install this. 4 | You could install this to get code completions (i.e. intellisense). 5 | 6 | ```javascript 7 | const {display} = require('node-kernel'); 8 | display.text('Hello World'); 9 | ``` 10 | 11 | ```javascript 12 | // Generate markdown outputs. 13 | const {display} = require('node-kernel'); 14 | display.markdown('# Markdown in notebooks'); 15 | display.markdown('Click [here](https://github.com)'); 16 | ``` 17 | 18 | ```javascript 19 | // Rich HTML output (with javascript). 20 | const {display} = require('node-kernel'); 21 | const buttonText = 'Click me'; 22 | display.html(` 23 | 24 | 30 | `); 31 | ``` 32 | 33 | ```javascript 34 | // View images. 35 | import * as fetch from 'node-fetch' 36 | const svgImage = await fetch('https://nodejs.org/static/images/logo.svg').then(res => res.text()); 37 | svgImage // Or use `display.image(svgImage);` 38 | ``` 39 | -------------------------------------------------------------------------------- /resources/docs/basics/richOutput.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "javascript", 5 | "source": [ 6 | "// This module is built into the notebook. You do not need to install this.\n// You could install this to get code completion (i.e. intellisense).\n\nconst {display} = require('node-kernel');" 7 | ], 8 | "outputs": [] 9 | }, 10 | { 11 | "language": "markdown", 12 | "source": [ 13 | "# Plain text output" 14 | ], 15 | "outputs": [] 16 | }, 17 | { 18 | "language": "javascript", 19 | "source": [ 20 | "// Plain text outputs (boring, same as `console.log`)\ndisplay.text('Hello World');" 21 | ], 22 | "outputs": [] 23 | }, 24 | { 25 | "language": "markdown", 26 | "source": [ 27 | "# Markdown output" 28 | ], 29 | "outputs": [] 30 | }, 31 | { 32 | "language": "javascript", 33 | "source": [ 34 | "// Use javascript to generate a list of markdown bullet points.\nconst toc = ['Index', 'Getting Started', 'Help', 'Glossary'];\nconst items = toc.map(item => `* ${item}`).join('\\n');\ndisplay.markdown('# Markdown generated in js and displayed in notebooks\\n' + items);" 35 | ], 36 | "outputs": [] 37 | }, 38 | { 39 | "language": "markdown", 40 | "source": [ 41 | "# HTML & javascript outputs" 42 | ], 43 | "outputs": [] 44 | }, 45 | { 46 | "language": "javascript", 47 | "source": [ 48 | "// Rich HTML output.\nconst buttonText = 'Click me';\ndisplay.html(`\n \n \n`)" 49 | ], 50 | "outputs": [] 51 | }, 52 | { 53 | "language": "html", 54 | "source": [ 55 | "\n
\n This is plain HTML with some divs and buttons, make note of the language of this cell, its `HTML`.\n

\n \n
" 56 | ], 57 | "outputs": [] 58 | }, 59 | { 60 | "language": "javascript", 61 | "source": [ 62 | "// In node.js we can generate some JavaScript that will interact with the above HTML.\ndisplay.javascript(`\n document.getElementById('anotherButton').addEventListener(\"click\", () => {\n console.log('Clicked another button');\n document.getElementById('myOutput').innerHTML = 'You clicked the button';\n });\n`);\n\n// Run this cell, and click the button in the previous cell." 63 | ], 64 | "outputs": [] 65 | }, 66 | { 67 | "language": "markdown", 68 | "source": [ 69 | "# Images" 70 | ], 71 | "outputs": [] 72 | }, 73 | { 74 | "language": "shellscript", 75 | "source": [ 76 | "# # First install node-fetch, if you don't already have it.\n# # Un comment the below line & run this cell.\n# npm i node-fetch" 77 | ], 78 | "outputs": [] 79 | }, 80 | { 81 | "language": "typescript", 82 | "source": [ 83 | "import * as fetch from 'node-fetch'" 84 | ], 85 | "outputs": [] 86 | }, 87 | { 88 | "language": "typescript", 89 | "source": [ 90 | "const svgImage = await fetch('https://nodejs.org/static/images/logo.svg').then(res => res.text());\nconsole.log(`svgImage is of type '${typeof svgImage}`);\nsvgImage\n\n// Note, you can switch the mime type and view the raw text representation of the content." 91 | ], 92 | "outputs": [] 93 | }, 94 | { 95 | "language": "typescript", 96 | "source": [ 97 | "const res = await fetch('https://github.githubassets.com/images/modules/logos_page/Octocat.png');\nconst arrayBuffer = await res.arrayBuffer();\nconst buffer = Buffer.from(arrayBuffer)\nbuffer;\n\n// The format of the image is detected automatically and displayed in the output." 98 | ], 99 | "outputs": [] 100 | }, 101 | { 102 | "language": "typescript", 103 | "source": [ 104 | "// Base64 encoded images are also understood and displayed as images instead of base64 string.\n// If you wish to have a look at the base64 string, you can switch the mime types from the image to text.\n\nconst base64String = buffer.toString('base64');\nconst encodedImages = `data:image/png;base64,${base64String}`;\nencodedImages" 105 | ], 106 | "outputs": [] 107 | }, 108 | { 109 | "language": "typescript", 110 | "source": [ 111 | "" 112 | ], 113 | "outputs": [] 114 | } 115 | ] 116 | } -------------------------------------------------------------------------------- /resources/docs/basics/sample.md: -------------------------------------------------------------------------------- 1 | # Run javascript node.js 2 | 3 | 1. Add the following cell and run the cell 4 | 5 | ```javascript 6 | function add(a, b){ 7 | return a + b; 8 | } 9 | console.log(add(1, 2)); 10 | ``` 11 | 12 | 13 | 2. Import external javascript/typescript modules. 14 | * Create a file named `sample.js` with the following contents: 15 | 16 | ```javascript 17 | module.exports.add = function add(a, b){ 18 | return a + b; 19 | } 20 | ``` 21 | 22 | Optionally you could create a typescript file named `sample.ts` as follows: 23 | 24 | ```typescript 25 | function add(a: number, b: number){ 26 | return a + b; 27 | } 28 | ``` 29 | 30 | * Import and execute the above module in a notebook. 31 | * Create a cell in a notebook and run this cell 32 | 33 | ```javascript 34 | const sample = require('./sample'); 35 | console.log(sample.add(1,2)); 36 | ``` 37 | 38 | ![Sample](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/basics/sample.png) 39 | -------------------------------------------------------------------------------- /resources/docs/basics/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/basics/sample.png -------------------------------------------------------------------------------- /resources/docs/basics/samples.md: -------------------------------------------------------------------------------- 1 | # Sample notebooks 2 | 3 | Use the command [Notebook: Open a sample node.js notebook](command:node.notebook.sample) to select from a number of available sample notebooks. 4 | Sample notebooks cover various features such as: 5 | * Tensorflow.js training 6 | * Tensorflow.js visualizations 7 | * Rich outputs 8 | * Plotly plots 9 | * Danfo.js (dataframes & plots) 10 | * Shell scripts 11 | * Tips n tricks for best experience in notebooks 12 | -------------------------------------------------------------------------------- /resources/docs/basics/shellScripts.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "markdown", 5 | "source": [ 6 | "Though node.js notebooks supports `shellscript` and `powershell`, they both end up getting executed using the same terminal [node-pty](https://github.com/microsoft/node-pty).\n\nThe two languages are provided for convenience (better intellisense and the like if one prefers to use `powershell` over `bash` or vice versa)." 7 | ], 8 | "outputs": [] 9 | }, 10 | { 11 | "language": "shellscript", 12 | "source": [ 13 | "## You can run shell scripts\nls" 14 | ], 15 | "outputs": [] 16 | }, 17 | { 18 | "language": "shellscript", 19 | "source": [ 20 | "## You can nwo install npm packages directly from a notebook.\n## These scripts run a little slower due to the fact terminals take a while to startup.\nnpm install mocha" 21 | ], 22 | "outputs": [] 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /resources/docs/basics/tips.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "markdown", 5 | "source": [ 6 | "# Better code completions" 7 | ], 8 | "outputs": [] 9 | }, 10 | { 11 | "language": "markdown", 12 | "source": [ 13 | "When using ES6 style import statements, you will not get any code completions in other cells.\n\n**Solution**: Use common js style imports as follows." 14 | ], 15 | "outputs": [] 16 | }, 17 | { 18 | "language": "typescript", 19 | "source": [ 20 | "const fs = require('fs') as typeof import('fs');" 21 | ], 22 | "outputs": [] 23 | }, 24 | { 25 | "language": "typescript", 26 | "source": [ 27 | "// You will get code completions for `fs.`\nfs." 28 | ], 29 | "outputs": [] 30 | }, 31 | { 32 | "language": "markdown", 33 | "source": [ 34 | "# Unnecessary warnings" 35 | ], 36 | "outputs": [] 37 | }, 38 | { 39 | "language": "markdown", 40 | "source": [ 41 | "If you have two notebooks opened and both end up with the same imports or same variables/constants, you'll get warnings from VS Code or the typescript language server.\n\n**Solution**: Keep one notebook open." 42 | ], 43 | "outputs": [] 44 | }, 45 | { 46 | "language": "typescript", 47 | "source": [ 48 | "const a = 'Hello World';" 49 | ], 50 | "outputs": [] 51 | }, 52 | { 53 | "language": "markdown", 54 | "source": [ 55 | "Open another notebook and add a variable/constant named `a` and watch how you end up with warnings. Now close the other notebooks except this." 56 | ], 57 | "outputs": [] 58 | } 59 | ] 60 | } -------------------------------------------------------------------------------- /resources/docs/danfojs/embedInDiv.md: -------------------------------------------------------------------------------- 1 | # This sample shows how we can render some HTML, and in the html have a placeholder for the plot 2 | 3 | 1. Create an HTML cell with the following markup & run this. 4 | 5 | ```html 6 | 17 |
18 |
19 | This is a sample plot. Note, initially there is plot. 20 | However we have an empty div with the id `myDiv`. 21 | The danfojs renderer will first look for an id you have specified, if found it will render the plot in that HTML 22 | Element, if not found 23 | it will render the plot in the output of the same cell which contains the code for the plot. 24 |
25 |
26 |
27 | ``` 28 | 29 | 2. Next create a JavaScript cell with the following code and run it 30 | 31 | ```javascript 32 | import * as dfd from 'danfojs-node'; 33 | const df = await dfd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv") 34 | 35 | var layout = { 36 | title: 'A financial charts', 37 | xaxis: { 38 | title: 'Date', 39 | }, 40 | yaxis: { 41 | title: 'Count', 42 | } 43 | } 44 | 45 | new_df = df.set_index({ key: "Date" }) 46 | 47 | // Note, the div is defined in the HTML of the previous cell, hence the Plot will be displayed above. 48 | new_df.plot("plot_div").line({ columns: ["AAPL.Open", "AAPL.High"], layout: layout }) 49 | ``` 50 | 51 | 3. Note how the plot is injected into the HTMl generated by the first cell (because the element named `plot_div` exists in the output of the first cell). 52 | 53 | ![Output](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/danfojs/embedInDiv.png) 54 | -------------------------------------------------------------------------------- /resources/docs/danfojs/embedInDiv.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "markdown", 5 | "source": [ 6 | "## This sample shows how we can render some HTML, and in the html have a placeholder for the plot\n" 7 | ], 8 | "outputs": [] 9 | }, 10 | { 11 | "language": "shellscript", 12 | "source": [ 13 | "## Uncomment this to install missing dependencies\n# npm install danfojs-node" 14 | ], 15 | "outputs": [] 16 | }, 17 | { 18 | "language": "html", 19 | "source": [ 20 | "\n
\n
\n This is a sample plot. Note, initially there is no plot.\n However we have an empty div with the id `myDiv`.\n The plotly renderer will first look for an id you have specified, if found it will render the plot in that HTML\n Element, if not found\n it will render the plot in the output of the same cell which contains the code for the plot.\n
\n
\n
" 21 | ], 22 | "outputs": [] 23 | }, 24 | { 25 | "language": "typescript", 26 | "source": [ 27 | "const dfd = require('danfojs-node') as typeof import('danfojs-node');\nconst df = await dfd.read_csv(\"https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv\")\n\nconst layout = {\n title: 'A financial charts',\n xaxis: {\n title: 'Date',\n },\n yaxis: {\n title: 'Count',\n }\n}\n\nconst new_df = df.set_index({ key: \"Date\" })\n// Note, the div is defined in the HTML of the previous cell, hence the Plot will be displayed above.\nnew_df.plot(\"myDiv\").line({ columns: [\"AAPL.Open\", \"AAPL.High\"], layout: layout })\n" 28 | ], 29 | "outputs": [] 30 | }, 31 | { 32 | "language": "typescript", 33 | "source": [ 34 | "import {display} from 'node-kernel';\ndisplay.html(`\n This is a sample plot. Note, initially there is plot.\n However we have an empty div with the id \"myDiv\". The plot will be rendered within this HTML element.\n
`);" 35 | ], 36 | "outputs": [] 37 | }, 38 | { 39 | "language": "javascript", 40 | "source": [ 41 | "new_df.plot(\"mySecondDiv\").line({ columns: [\"AAPL.Open\", \"AAPL.High\"], layout: layout })" 42 | ], 43 | "outputs": [] 44 | }, 45 | { 46 | "language": "javascript", 47 | "source": [ 48 | "" 49 | ], 50 | "outputs": [] 51 | } 52 | ] 53 | } -------------------------------------------------------------------------------- /resources/docs/danfojs/embedInDiv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/danfojs/embedInDiv.png -------------------------------------------------------------------------------- /resources/docs/danfojs/htmlOutput.md: -------------------------------------------------------------------------------- 1 | # Get Html Output from [danfo.js](https://danfo.jsdata.org/) 2 | [danfo.js](https://danfo.jsdata.org/) prints ASCII tables for console windows, however this extension changes that behavior to give you pretty HTML. 3 | 4 | ```js 5 | const dfd = require('danfojs-node'); 6 | const df = await dfd.read_csv("https://web.stanford.edu/class/archive/cs/cs109/cs109.1166/stuff/titanic.csv"); 7 | df.head().print(); 8 | ``` 9 | 10 | ![Html Output](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/danfojs/htmlOutput.png) 11 | 12 | 13 | ```js 14 | const dfd = require('danfojs-node'); 15 | const s = new dfd.Series([1, 3, 5, undefined, 6, 8]) 16 | s.print() 17 | ``` 18 | 19 | ![Series as HTML](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/danfojs/htmlOutputSeries.png) 20 | 21 | 22 | -------------------------------------------------------------------------------- /resources/docs/danfojs/htmlOutput.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "shellscript", 5 | "source": [ 6 | "## Uncomment this to install missing dependencies\n# npm install danfojs-node" 7 | ], 8 | "outputs": [] 9 | }, 10 | { 11 | "language": "typescript", 12 | "source": [ 13 | "const dfd = require('danfojs-node') as typeof import('danfojs-node');\nconst df = await dfd.read_csv(\"https://web.stanford.edu/class/archive/cs/cs109/cs109.1166/stuff/titanic.csv\");\ndf.head().print();" 14 | ], 15 | "outputs": [] 16 | }, 17 | { 18 | "language": "javascript", 19 | "source": [ 20 | "const s = new dfd.Series([1, 3, 5, undefined, 6, 8])\ns.print()" 21 | ], 22 | "outputs": [] 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /resources/docs/danfojs/htmlOutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/danfojs/htmlOutput.png -------------------------------------------------------------------------------- /resources/docs/danfojs/htmlOutputSeries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/danfojs/htmlOutputSeries.png -------------------------------------------------------------------------------- /resources/docs/danfojs/plots.md: -------------------------------------------------------------------------------- 1 | # Generate plots using danfo.js in node.js 2 | 3 | ```js 4 | const dfd = require('danfojs-node') as typeof import('danfojs-node'); 5 | const df = await dfd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv") 6 | 7 | const layout = { 8 | title: 'A financial charts', 9 | xaxis: { 10 | title: 'Date', 11 | }, 12 | yaxis: { 13 | title: 'Count', 14 | } 15 | } 16 | 17 | const new_df = df.set_index({ key: "Date" }) 18 | new_df.plot("plot_div").line({ columns: ["AAPL.Open", "AAPL.High"], layout: layout }) 19 | ``` 20 | 21 | ![Sample](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/danfojs/plots.png) 22 | -------------------------------------------------------------------------------- /resources/docs/danfojs/plots.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "shellscript", 5 | "source": [ 6 | "## Uncomment this to install missing dependencies\n# npm install danfojs-node" 7 | ], 8 | "outputs": [] 9 | }, 10 | { 11 | "language": "typescript", 12 | "source": [ 13 | "const dfd = require('danfojs-node') as typeof import('danfojs-node');\nconst df = await dfd.read_csv(\"https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv\")\n\nconst layout = {\n title: 'A financial charts',\n xaxis: {\n title: 'Date',\n },\n yaxis: {\n title: 'Count',\n }\n}\n\nconst new_df = df.set_index({ key: \"Date\" })\nnew_df.plot(\"plot_div\").line({ columns: [\"AAPL.Open\", \"AAPL.High\"], layout: layout })" 14 | ], 15 | "outputs": [] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /resources/docs/danfojs/plots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/danfojs/plots.png -------------------------------------------------------------------------------- /resources/docs/plotly/embedInDiv.md: -------------------------------------------------------------------------------- 1 | # This sample shows how we can render some HTML, and in the html have a placeholder for the plot 2 | 3 | 1. Create an HTML cell with the following markup & run this. 4 | 5 | ```html 6 | 17 |
18 |
19 | This is a sample plot. Note, initially there is plot. 20 | However we have an empty div with the id `myDiv`. 21 | The plotly renderer will first look for an id you have specified, if found it will render the plot in that HTML 22 | Element, if not found 23 | it will render the plot in the output of the same cell which contains the code for the plot. 24 |
25 |
26 |
27 | ``` 28 | 29 | 2. Next create a JavaScript cell with the following code and run it 30 | 31 | ```javascript 32 | const { Plotly } = require('node-kernel'); 33 | var data = [{ 34 | values: [19, 26, 55], 35 | labels: ['Residential', 'Non-Residential', 'Utility'], 36 | type: 'pie' 37 | }]; 38 | var layout = { 39 | height: 400, 40 | width: 500 41 | }; 42 | 43 | // Note, the div is defined in the HTML of the previous cell, hence the Plot will be displayed above. 44 | Plotly.newPlot('myDiv', data, layout); 45 | ``` 46 | 47 | 3. Note how the plot is injected into the HTMl generated by the first cell (because the element named `myDiv` exists in the output of the first cell). 48 | 49 | ![Output](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/plotly/embedInDiv.png) 50 | -------------------------------------------------------------------------------- /resources/docs/plotly/embedInDiv.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "markdown", 5 | "source": [ 6 | "## This sample shows how we can render some HTML, and in the html have a placeholder for the plot\n" 7 | ], 8 | "outputs": [] 9 | }, 10 | { 11 | "language": "html", 12 | "source": [ 13 | "\n
\n
\n This is a sample plot. Note, initially there is no plot.\n However we have an empty div with the id `myDiv`.\n The plotly renderer will first look for an id you have specified, if found it will render the plot in that HTML\n Element, if not found\n it will render the plot in the output of the same cell which contains the code for the plot.\n
\n
\n
" 14 | ], 15 | "outputs": [] 16 | }, 17 | { 18 | "language": "javascript", 19 | "source": [ 20 | "const { Plotly } = require('node-kernel');\nvar data = [{\n values: [19, 26, 55],\n labels: ['Residential', 'Non-Residential', 'Utility'],\n type: 'pie'\n}];\nvar layout = {\n height: 200,\n width: 300,\n margin: {l:20, t:20, b:20, r:20}\n};\n\n// Note, the div is defined in the HTML of the previous cell, hence the Plot will be displayed above.\nPlotly.newPlot('myDiv', data, layout);" 21 | ], 22 | "outputs": [] 23 | }, 24 | { 25 | "language": "typescript", 26 | "source": [ 27 | "import {display} from 'node-kernel';\ndisplay.html(`\n This is a sample plot. Note, initially there is plot.\n However we have an empty div with the id \"myDiv\". The plot will be rendered within this HTML element.\n
`);" 28 | ], 29 | "outputs": [] 30 | }, 31 | { 32 | "language": "javascript", 33 | "source": [ 34 | "Plotly.newPlot('mySecondDiv', data, layout);" 35 | ], 36 | "outputs": [] 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /resources/docs/plotly/embedInDiv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/plotly/embedInDiv.png -------------------------------------------------------------------------------- /resources/docs/plotly/generate.md: -------------------------------------------------------------------------------- 1 | # Generate and view Plotly plots in Notebooks 2 | 3 | The extension ships with the latest version of Plotly, making it unnecessary to install plotly.js. 4 | 5 | ```javascript 6 | const { Plotly } = require('node-kernel'); 7 | const data = [{ 8 | values: [19, 26, 55], 9 | labels: ['Residential', 'Non-Residential', 'Utility'], 10 | type: 'pie' 11 | }]; 12 | const layout = { 13 | height: 400, 14 | width: 500 15 | }; 16 | // There is no HTML element named `myDiv`, hence the plot is displayed below. 17 | Plotly.newPlot('myDiv', data, layout); 18 | ``` 19 | 20 | ![Sample](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/plotly/generate.png) 21 | -------------------------------------------------------------------------------- /resources/docs/plotly/generate.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "markdown", 5 | "source": [ 6 | "The extension ships with the latest version of Plotly, making it unnecessary to install plotly.js." 7 | ], 8 | "outputs": [] 9 | }, 10 | { 11 | "language": "javascript", 12 | "source": [ 13 | "const { Plotly } = require('node-kernel');\nconst data = [{\n values: [19, 26, 55],\n labels: ['Residential', 'Non-Residential', 'Utility'],\n type: 'pie'\n}];\nconst layout = {\n height: 400,\n width: 500\n};\n// There is no HTML element named `myDiv`, hence the plot is displayed below.\nPlotly.newPlot('myDiv', data, layout);" 14 | ], 15 | "outputs": [] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /resources/docs/plotly/generate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/plotly/generate.png -------------------------------------------------------------------------------- /resources/docs/plotly/saveToFile.md: -------------------------------------------------------------------------------- 1 | # Save a plot directly to a file 2 | 3 | 1. Create a file with the extension `*.nnb` (e.g. `sample.nnb`) 4 | 2. Add the following code and run it 5 | 6 | ![Sample](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/plotly/saveToFile.png) 7 | -------------------------------------------------------------------------------- /resources/docs/plotly/saveToFile.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "javascript", 5 | "source": [ 6 | "const { Plotly } = require('node-kernel');\nconst data = [{\n values: [19, 26, 55],\n labels: ['Residential', 'Non-Residential', 'Utility'],\n type: 'pie'\n}];\nconst layout = {\n height: 400,\n width: 500\n};\n\n// You could provide he path where the image needs to be written into.\n// await Plotly.toFile(data, layout, 'png', '');\n\n// Or don't provide anything, and it will be written into a temporary file.\nconst tempImageFileName = await Plotly.toFile(data, layout, 'png');\nconsole.log(tempImageFileName);" 7 | ], 8 | "outputs": [] 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /resources/docs/plotly/saveToFile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/plotly/saveToFile.png -------------------------------------------------------------------------------- /resources/docs/tensorflow/mnist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/tensorflow/mnist.png -------------------------------------------------------------------------------- /resources/docs/tensorflow/modelSummary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/tensorflow/modelSummary.png -------------------------------------------------------------------------------- /resources/docs/tensorflow/sample.md: -------------------------------------------------------------------------------- 1 | # Use tensorflow.js along with [tensorflow visualizations](https://www.npmjs.com/package/@tensorflow/tfjs-vis) within node.js 2 | 3 | Use tensorflow.js to train your models and create rich visualizations using [@tensorflow/tfjs-vis](https://www.npmjs.com/package/@tensorflow/tfjs-vis) in node.js environments just as you would in the browser environment. 4 | 5 | ![mnist](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/tensorflow/mnist.png) 6 | -------------------------------------------------------------------------------- /resources/docs/tensorflow/sample.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "shellscript", 5 | "source": [ 6 | "## Uncomment this to install missing dependencies\n# npm i @tensorflow/tfjs-node @tensorflow/tfjs-vis\n# npm i isomorphic-fetch" 7 | ], 8 | "outputs": [] 9 | }, 10 | { 11 | "language": "typescript", 12 | "source": [ 13 | "import * as tfvis from '@tensorflow/tfjs-vis'\nimport * as tf from '@tensorflow/tfjs-node'\nimport fetch from 'isomorphic-fetch';\n" 14 | ], 15 | "outputs": [] 16 | }, 17 | { 18 | "language": "javascript", 19 | "source": [ 20 | "/**\n * Get the car data reduced to just the variables we are interested\n * and cleaned of missing data.\n */\nasync function getData() {\n const carsDataResponse = await fetch('https://storage.googleapis.com/tfjs-tutorials/carsData.json');\n const carsData = await carsDataResponse.json();\n const cleaned = carsData.map(car => ({\n mpg: car.Miles_per_Gallon,\n horsepower: car.Horsepower,\n }))\n .filter(car => (car.mpg != null && car.horsepower != null));\n\n return cleaned;\n}" 21 | ], 22 | "outputs": [] 23 | }, 24 | { 25 | "language": "javascript", 26 | "source": [ 27 | "async function run() {\n const values = data.map(d => ({\n x: d.horsepower,\n y: d.mpg,\n }));\n\n tfvis.render.scatterplot(\n { name: 'Horsepower v MPG' },\n { values },\n {\n xLabel: 'Horsepower',\n yLabel: 'MPG',\n height: 300,\n width: 500\n }\n );\n\n // More code will be added below\n}\n// Load and plot the original input data that we are going to train on.\nconst data = await getData();\nawait run()" 28 | ], 29 | "outputs": [] 30 | }, 31 | { 32 | "language": "javascript", 33 | "source": [ 34 | "function createModel() {\n // Create a sequential model\n const model = tf.sequential();\n\n // Add a single input layer\n model.add(tf.layers.dense({ inputShape: [1], units: 1, useBias: true }));\n\n // Add an output layer\n model.add(tf.layers.dense({ units: 1, useBias: true }));\n\n return model;\n}" 35 | ], 36 | "outputs": [] 37 | }, 38 | { 39 | "language": "javascript", 40 | "source": [ 41 | "// Create the model\nconst model = createModel();\ntfvis.show.modelSummary({ name: 'Model Summary' }, model);" 42 | ], 43 | "outputs": [] 44 | }, 45 | { 46 | "language": "javascript", 47 | "source": [ 48 | "/**\n * Convert the input data to tensors that we can use for machine\n * learning. We will also do the important best practices of _shuffling_\n * the data and _normalizing_ the data\n * MPG on the y-axis.\n */\nfunction convertToTensor(data) {\n // Wrapping these calculations in a tidy will dispose any\n // intermediate tensors.\n\n return tf.tidy(() => {\n // Step 1. Shuffle the data\n tf.util.shuffle(data);\n\n // Step 2. Convert data to Tensor\n const inputs = data.map(d => d.horsepower)\n const labels = data.map(d => d.mpg);\n\n const inputTensor = tf.tensor2d(inputs, [inputs.length, 1]);\n const labelTensor = tf.tensor2d(labels, [labels.length, 1]);\n\n //Step 3. Normalize the data to the range 0 - 1 using min-max scaling\n const inputMax = inputTensor.max();\n const inputMin = inputTensor.min();\n const labelMax = labelTensor.max();\n const labelMin = labelTensor.min();\n\n const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin));\n const normalizedLabels = labelTensor.sub(labelMin).div(labelMax.sub(labelMin));\n\n return {\n inputs: normalizedInputs,\n labels: normalizedLabels,\n // Return the min/max bounds so we can use them later.\n inputMax,\n inputMin,\n labelMax,\n labelMin,\n }\n });\n}" 49 | ], 50 | "outputs": [] 51 | }, 52 | { 53 | "language": "javascript", 54 | "source": [ 55 | "async function trainModel(model, inputs, labels) {\n // Prepare the model for training.\n model.compile({\n optimizer: tf.train.adam(),\n loss: tf.losses.meanSquaredError,\n metrics: ['mse'],\n });\n\n const batchSize = 32;\n const epochs = 50;\n\n return await model.fit(inputs, labels, {\n batchSize,\n epochs,\n shuffle: true,\n callbacks: tfvis.show.fitCallbacks(\n { name: 'Training Performance' },\n ['loss', 'mse'],\n { height: 200, width: 500, callbacks: ['onEpochEnd'],\n verbose: false }\n )\n });\n}\n" 56 | ], 57 | "outputs": [] 58 | }, 59 | { 60 | "language": "javascript", 61 | "source": [ 62 | "// Convert the data to a form we can use for training.\nconst tensorData = convertToTensor(data);\nconst { inputs, labels } = tensorData;\n// Train the model\nawait trainModel(model, inputs, labels);" 63 | ], 64 | "outputs": [] 65 | }, 66 | { 67 | "language": "markdown", 68 | "source": [ 69 | "# Testing the model" 70 | ], 71 | "outputs": [] 72 | }, 73 | { 74 | "language": "javascript", 75 | "source": [ 76 | "function testModel(model, inputData, normalizationData) {\n const { inputMax, inputMin, labelMin, labelMax } = normalizationData;\n\n // Generate predictions for a uniform range of numbers between 0 and 1;\n // We un-normalize the data by doing the inverse of the min-max scaling\n // that we did earlier.\n const [xs, preds] = tf.tidy(() => {\n\n const xs = tf.linspace(0, 1, 100);\n const preds = model.predict(xs.reshape([100, 1]));\n\n const unNormXs = xs\n .mul(inputMax.sub(inputMin))\n .add(inputMin);\n\n const unNormPreds = preds\n .mul(labelMax.sub(labelMin))\n .add(labelMin);\n\n // Un-normalize the data\n return [unNormXs.dataSync(), unNormPreds.dataSync()];\n });\n\n\n const predictedPoints = Array.from(xs).map((val, i) => {\n return { x: val, y: preds[i] }\n });\n\n const originalPoints = inputData.map(d => ({\n x: d.horsepower, y: d.mpg,\n }));\n\n\n tfvis.render.scatterplot(\n { name: 'Model Predictions vs Original Data' },\n { values: [originalPoints, predictedPoints], series: ['original', 'predicted'] },\n {\n xLabel: 'Horsepower',\n yLabel: 'MPG',\n height: 300,\n width:500\n }\n );\n}" 77 | ], 78 | "outputs": [] 79 | }, 80 | { 81 | "language": "javascript", 82 | "source": [ 83 | "// Make some predictions using the model and compare them to the\n// original data\ntestModel(model, data, tensorData);\n" 84 | ], 85 | "outputs": [] 86 | } 87 | ] 88 | } -------------------------------------------------------------------------------- /resources/docs/tensorflow/scatterPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/tensorflow/scatterPlot.png -------------------------------------------------------------------------------- /resources/docs/tensorflow/scatterPlot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/tensorflow/scatterPlot2.png -------------------------------------------------------------------------------- /resources/docs/tensorflow/tensorboard.md: -------------------------------------------------------------------------------- 1 | # Run tensorflow within a notebook and view the [Tensorboard](https://www.tensorflow.org/tensorboard) within in VS Code 2 | 3 | 1. Install the [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) 4 | 2. Train the model using tensorflow.js ([sample](https://js.tensorflow.org/api_node/1.0.2/#node.tensorBoard)) 5 | 2. Launch the tensorboard using the command `Python: Launch TensorBoard` 6 | 7 | ![Tensorboard](https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/main/resources/docs/tensorflow/tensorboard.png) 8 | -------------------------------------------------------------------------------- /resources/docs/tensorflow/tensorboard.nnb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "language": "markdown", 5 | "source": [ 6 | "* Install [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python)\n * More info about tensorboards in Python extension can be found [here](https://code.visualstudio.com/docs/datascience/pytorch-support#_tensorboard-integration)\n* Install `tensorboard` into your Python environment\n* Run the following cells (sample borrowed from here [https://js.tensorflow.org/api_node/1.0.2/#node.tensorBoard](https://js.tensorflow.org/api_node/1.0.2/#node.tensorBoard)\n* After running the cells, confirm the directory `/tmp/fit_logs_` exists in your workspace folder\n* Launch the tensorboard using the command `Python: Launch TensorBoard`." 7 | ], 8 | "outputs": [] 9 | }, 10 | { 11 | "language": "typescript", 12 | "source": [ 13 | "import * as tf from '@tensorflow/tfjs-node'\nimport * as path from 'path';" 14 | ], 15 | "outputs": [] 16 | }, 17 | { 18 | "language": "javascript", 19 | "source": [ 20 | "// Constructor a toy multilayer-perceptron regressor for demo purpose.\nconst model = tf.sequential();\nmodel.add(\n tf.layers.dense({ units: 100, activation: 'relu', inputShape: [200] }));\nmodel.add(tf.layers.dense({ units: 1 }));\nmodel.compile({\n loss: 'meanSquaredError',\n optimizer: 'sgd',\n metrics: ['MAE']\n});\n\n// Generate some random fake data for demo purpose.\nconst xs = tf.randomUniform([10000, 200]);\nconst ys = tf.randomUniform([10000, 1]);\nconst valXs = tf.randomUniform([1000, 200]);\nconst valYs = tf.randomUniform([1000, 1]);\n\n// Start model training process.\nvoid await model.fit(xs, ys, {\n epochs: 10,\n validationData: [valXs, valYs],\n // Add the tensorBoard callback here.\n callbacks: tf.node.tensorBoard(path.join(__dirname, 'tmp/fit_logs_1'))\n});" 21 | ], 22 | "outputs": [] 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /resources/docs/tensorflow/tensorboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/tensorflow/tensorboard.png -------------------------------------------------------------------------------- /resources/docs/tensorflow/tfvis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/docs/tensorflow/tfvis.png -------------------------------------------------------------------------------- /resources/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/icons/logo.png -------------------------------------------------------------------------------- /resources/icons/tf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DonJayamanne/typescript-notebook/d3c419b635ba2c88179cef3ebf0ecf58563f2410/resources/icons/tf.png -------------------------------------------------------------------------------- /resources/icons/tf.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 32 | 37 | 42 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /resources/scripts/README.md: -------------------------------------------------------------------------------- 1 | `ts-node` is shipped with this extension. 2 | All of `node_modules` is what is required to get `ts-node` up and running. 3 | -------------------------------------------------------------------------------- /resources/scripts/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@cspotcode/source-map-consumer": { 8 | "version": "0.8.0", 9 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", 10 | "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==" 11 | }, 12 | "@cspotcode/source-map-support": { 13 | "version": "0.7.0", 14 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", 15 | "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", 16 | "requires": { 17 | "@cspotcode/source-map-consumer": "0.8.0" 18 | } 19 | }, 20 | "@tsconfig/node10": { 21 | "version": "1.0.8", 22 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", 23 | "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" 24 | }, 25 | "@tsconfig/node12": { 26 | "version": "1.0.9", 27 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", 28 | "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" 29 | }, 30 | "@tsconfig/node14": { 31 | "version": "1.0.1", 32 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", 33 | "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" 34 | }, 35 | "@tsconfig/node16": { 36 | "version": "1.0.2", 37 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", 38 | "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" 39 | }, 40 | "acorn": { 41 | "version": "8.5.0", 42 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", 43 | "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==" 44 | }, 45 | "acorn-walk": { 46 | "version": "8.2.0", 47 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 48 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" 49 | }, 50 | "arg": { 51 | "version": "4.1.3", 52 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 53 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" 54 | }, 55 | "create-require": { 56 | "version": "1.1.1", 57 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 58 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" 59 | }, 60 | "diff": { 61 | "version": "4.0.2", 62 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 63 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" 64 | }, 65 | "make-error": { 66 | "version": "1.3.6", 67 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 68 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" 69 | }, 70 | "node-addon-api": { 71 | "version": "3.2.1", 72 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", 73 | "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" 74 | }, 75 | "profoundjs-node-pty": { 76 | "version": "2.0.3", 77 | "resolved": "https://registry.npmjs.org/profoundjs-node-pty/-/profoundjs-node-pty-2.0.3.tgz", 78 | "integrity": "sha512-vAAONSX9GhTNXHfoJxb6l4MTN4LDa1G04hb8n5REOkzmZMbe+rtK2onQNx72gdfxfgckzZ5t+/CN0VUvnBC9wA==", 79 | "requires": { 80 | "node-addon-api": "^3.0.2" 81 | } 82 | }, 83 | "ts-node": { 84 | "version": "10.4.0", 85 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", 86 | "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", 87 | "requires": { 88 | "@cspotcode/source-map-support": "0.7.0", 89 | "@tsconfig/node10": "^1.0.7", 90 | "@tsconfig/node12": "^1.0.7", 91 | "@tsconfig/node14": "^1.0.0", 92 | "@tsconfig/node16": "^1.0.2", 93 | "acorn": "^8.4.1", 94 | "acorn-walk": "^8.1.1", 95 | "arg": "^4.1.0", 96 | "create-require": "^1.1.0", 97 | "diff": "^4.0.1", 98 | "make-error": "^1.1.1", 99 | "yn": "3.1.1" 100 | } 101 | }, 102 | "yn": { 103 | "version": "3.1.1", 104 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 105 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /resources/scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "shell.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "profoundjs-node-pty": "^2.0.3", 14 | "ts-node": "^10.4.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /resources/scripts/shell.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const cp = require('child_process'); 3 | const fs = require('fs'); 4 | 5 | const startSeparator = '51e9f0e8-77a0-4bf0-9733-335153be2ec0:Start'; 6 | const errorSeparator = '51e9f0e8-77a0-4bf0-9733-335153be2ec0:Error'; 7 | const endSeparator = '51e9f0e8-77a0-4bf0-9733-335153be2ec0:End'; 8 | 9 | try { 10 | const contentsFile = process.argv[2]; 11 | const contents = fs.readFileSync(contentsFile).toString(); 12 | process.stdout.write(startSeparator); 13 | cp.spawn(contents, { 14 | shell: true, 15 | cwd: process.cwd(), 16 | env: process.env, 17 | stdio: ['inherit', 'inherit', 'inherit'] 18 | }) 19 | .on('close', (code) => { 20 | process.stdout.write(endSeparator); 21 | process.exit(code); 22 | }) 23 | .on('exit', (code) => { 24 | process.stdout.write(endSeparator); 25 | process.exit(code); 26 | }); 27 | } catch (ex) { 28 | process.stdout.write(errorSeparator); 29 | process.stdout.write(util.format(ex)); 30 | process.stdout.write(endSeparator); 31 | process.exit(1); 32 | } 33 | -------------------------------------------------------------------------------- /src/client/common.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import * as tfvis from '@tensorflow/tfjs-vis'; 3 | import * as renderUtils from '@tensorflow/tfjs-vis/dist/render/render_utils'; 4 | import * as dom from '@tensorflow/tfjs-vis/dist/util/dom'; 5 | import vega_embed from 'vega-embed'; 6 | export async function valuesDistribution(container: any, stats: any, values: any) { 7 | tfvis.render.histogram(container, values, { height: 150, stats }); 8 | } 9 | 10 | export async function renderHeatmap(container: any, spec: any, embedOpts: any) { 11 | const drawArea = renderUtils.getDrawArea(container); 12 | vega_embed(drawArea, spec, embedOpts); 13 | } 14 | export async function renderLayer(container: any, layer: { details: any; weights: Record }) { 15 | const drawArea = renderUtils.getDrawArea(container); 16 | const details = layer.details; 17 | const headers = ['Weight Name', 'Shape', 'Min', 'Max', '# Params', '# Zeros', '# NaNs', '# Infinity']; 18 | // Show layer summary 19 | const weightsInfoSurface = dom.subSurface(drawArea, 'layer-weights-info'); 20 | const detailValues = details.map((l) => [ 21 | l.name, 22 | l.shape, 23 | l.stats.min, 24 | l.stats.max, 25 | l.weight.size, 26 | l.stats.numZeros, 27 | l.stats.numNans, 28 | l.stats.numInfs 29 | ]); 30 | tfvis.render.table(weightsInfoSurface, { headers, values: detailValues }); 31 | const histogramSelectorSurface = dom.subSurface(drawArea, 'select-layer'); 32 | const layerValuesHistogram = dom.subSurface(drawArea, 'param-distribution'); 33 | const handleSelection = (layerName: string) => { 34 | tfvis.render.histogram(layerValuesHistogram, layer.weights[layerName], { 35 | height: 150, 36 | width: 460, 37 | stats: false 38 | }); 39 | }; 40 | addHistogramSelector( 41 | details.map((d) => d.name), 42 | histogramSelectorSurface, 43 | handleSelection 44 | ); 45 | } 46 | function addHistogramSelector( 47 | items, 48 | parent, 49 | // tslint:disable-next-line:no-any 50 | selectionHandler 51 | ) { 52 | const select = ` 53 | 56 | `; 57 | const button = ``; 58 | const content = `
${button}${select}
`; 59 | parent.innerHTML = content; 60 | // Add listeners 61 | const buttonEl = parent.querySelector('button'); 62 | const selectEl = parent.querySelector('select'); 63 | buttonEl.addEventListener('click', () => { 64 | selectionHandler(selectEl.selectedOptions[0].label); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /src/client/index.css: -------------------------------------------------------------------------------- 1 | #tfjs-visor-container .visor { 2 | width: 100% !important; 3 | /* height: 100% !important; */ 4 | } 5 | #tfjs-visor-container .visor .visor-controls { 6 | display: none !important; 7 | } 8 | /* Fix for https://github.com/microsoft/vscode/issues/131339 */ 9 | table, 10 | th, 11 | tr { 12 | text-align: left; 13 | } 14 | 15 | /* Tensor flow table headings should not have a white background */ 16 | /* Problem is, in dark theme, the font color is white and this backtround makes the text difficult to read */ 17 | table.tf-table thead tr th, .subsurface-title, .tf-label { 18 | background-color: inherit !important; 19 | } 20 | -------------------------------------------------------------------------------- /src/client/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | // declare const tfvis: typeof import('@tensorflow/tfjs-vis'); 3 | import * as tfvis from '@tensorflow/tfjs-vis'; 4 | import type { fitCallbacks } from '@tensorflow/tfjs-vis/dist/show/history'; 5 | import './index.css'; 6 | import { deserialize } from '../extension/serializer'; 7 | import { TensorFlowVis, TensorFlowVisRequest } from '../extension/server/types'; 8 | import { renderHeatmap, renderLayer, valuesDistribution } from './common'; 9 | 10 | console.log('Inside VIS'); 11 | const api = acquireVsCodeApi(); 12 | window.addEventListener('message', (e) => onMessage(e.data)); 13 | api.postMessage({ type: 'loaded' }); 14 | 15 | function onMessage(data?: { _type?: 'helloWorld' } | TensorFlowVis | any) { 16 | if (!data || !data.type) { 17 | return; 18 | } 19 | switch (data.type) { 20 | case 'tensorFlowVis': { 21 | handleTensorFlowMessage(data); 22 | } 23 | } 24 | } 25 | 26 | const fitCallbackHandlersMappedByContianer = new Map>(); 27 | function handleTensorFlowMessage(message: TensorFlowVis) { 28 | switch (message.request) { 29 | case 'show': { 30 | tfvis.visor().open(); 31 | break; 32 | } 33 | case 'setactivetab': 34 | tfvis.visor().setActiveTab(message.tabName); 35 | break; 36 | case 'registerfitcallback': { 37 | const callbacks = tfvis.show.fitCallbacks(message.container as any, message.metrics); 38 | fitCallbackHandlersMappedByContianer.set(JSON.stringify(message.container), callbacks); 39 | break; 40 | } 41 | case 'history': { 42 | void tfvis.show.history(message.container as any, message.history as any, message.metrics, message.opts); 43 | break; 44 | } 45 | case 'perclassaccuracy': { 46 | tfvis.show.perClassAccuracy(message.container as any, message.classAccuracy, message.classLabels); 47 | break; 48 | } 49 | case 'layer': { 50 | void renderLayer(message.container as any, message.layer as any); 51 | break; 52 | } 53 | case 'fitcallback': { 54 | const callbacks = fitCallbackHandlersMappedByContianer.get(JSON.stringify(message.container)); 55 | if (!callbacks) { 56 | return console.error(`No callbacks registered for ${JSON.stringify(message.container)}`); 57 | } 58 | if (callbacks[message.handler]) { 59 | void callbacks[message.handler](message.iteration, message.log); 60 | } 61 | break; 62 | } 63 | case 'barchart': { 64 | void tfvis.render.barchart(message.container as any, message.data, message.opts); 65 | break; 66 | } 67 | case 'valuesdistribution': { 68 | valuesDistribution(message.container, message.tensor.stats, message.tensor.values); 69 | break; 70 | } 71 | case 'confusionmatrix': { 72 | void tfvis.render.confusionMatrix(message.container as any, message.data, message.opts); 73 | break; 74 | } 75 | case 'linechart': { 76 | void tfvis.render.linechart(message.container as any, message.data, message.opts); 77 | break; 78 | } 79 | case 'scatterplot': { 80 | void tfvis.render.scatterplot(message.container as any, message.data, message.opts); 81 | break; 82 | } 83 | case 'histogram': { 84 | const data = deserialize(message.data as unknown as string); 85 | const promise = tfvis.render.histogram(message.container as any, data, message.opts); 86 | if (message.requestId) { 87 | sendPromiseResult(promise, message.request, message.requestId); 88 | } 89 | break; 90 | } 91 | case 'heatmap': { 92 | const { spec, embedOpts } = message.data; 93 | renderHeatmap(message.container, spec, embedOpts); 94 | break; 95 | } 96 | case 'modelsummary': { 97 | const data = message.model; 98 | (data as any).layers = data.layers.map((layer) => { 99 | const newLayer = layer; 100 | newLayer.countParams = () => (layer as any).parameters; 101 | return newLayer; 102 | }); 103 | void tfvis.show.modelSummary(message.container as any, data); 104 | break; 105 | } 106 | default: 107 | break; 108 | } 109 | } 110 | 111 | function sendPromiseResult(promise: Promise, request: string, requestId: string) { 112 | promise 113 | .then(() => { 114 | api.postMessage({ 115 | request: 'history', 116 | requestId, 117 | success: true, 118 | type: 'tensorFlowVis' 119 | }); 120 | }) 121 | .catch((ex) => { 122 | const error = ex as Partial | undefined; 123 | api.postMessage({ 124 | request: 'history', 125 | requestId, 126 | success: false, 127 | type: 'tensorFlowVis', 128 | error: { 129 | message: error?.message, 130 | name: error?.name, 131 | stack: error?.stack 132 | } 133 | }); 134 | }); 135 | } 136 | -------------------------------------------------------------------------------- /src/client/plotGenerator.ts: -------------------------------------------------------------------------------- 1 | import { ActivationFunction, OutputItem } from 'vscode-notebook-renderer'; 2 | import { errorToJson } from '../extension/coreUtils'; 3 | import { GeneratePlot, ResponseType } from '../extension/server/types'; 4 | const Plotly = require('plotly.js-dist') as typeof import('plotly.js'); 5 | /* eslint-disable @typescript-eslint/no-explicit-any */ 6 | 7 | export const activate: ActivationFunction = (context) => { 8 | return { 9 | renderOutputItem(outputItem: OutputItem, element: HTMLElement) { 10 | const json: GeneratePlot = outputItem.json(); 11 | const existingEle = 12 | json.ele && typeof json.ele === 'string' ? document.getElementById(json.ele) : undefined; 13 | const ele = existingEle || document.createElement('div'); 14 | if (json.hidden) { 15 | element.style.display = 'none'; 16 | } 17 | if (!existingEle) { 18 | element.appendChild(ele); 19 | } 20 | Plotly.newPlot(ele, json.data, json.layout) 21 | .then((gd) => { 22 | console.log('Generated Plot', gd); 23 | if (!json.download || !json.requestId) { 24 | console.log('Nothing to do with Plot'); 25 | return; 26 | } 27 | console.log('Converting to bytes'); 28 | Plotly.toImage(gd, { 29 | format: json.format || 'png', 30 | height: json.layout?.height || 400, 31 | width: json.layout?.width || 500 32 | }) 33 | .then((url) => { 34 | console.log('Got Url'); 35 | if (!context.postMessage) { 36 | return; 37 | } 38 | console.log('sent Url'); 39 | context.postMessage({ 40 | type: 'plotGenerated', 41 | success: true, 42 | base64: url, 43 | requestId: json.requestId 44 | }); 45 | }) 46 | .catch((ex) => { 47 | console.log('Generating failed', ex); 48 | if (!context.postMessage) { 49 | return; 50 | } 51 | context.postMessage({ 52 | type: 'plotGenerated', 53 | success: false, 54 | error: errorToJson(ex), 55 | requestId: json.requestId 56 | }); 57 | }); 58 | }) 59 | .catch((ex) => { 60 | console.error('Failed to generate bytes of plot', ex); 61 | }); 62 | } 63 | }; 64 | }; 65 | -------------------------------------------------------------------------------- /src/extension/configuration.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | import { Configuration } from './server/types'; 3 | import * as tmp from 'tmp'; 4 | import { promises as fs } from "fs"; 5 | import { registerDisposable } from './utils'; 6 | 7 | export function getConfiguration(): Configuration { 8 | const config = workspace.getConfiguration('node_notebook', undefined); 9 | return { 10 | registerTsNode: config.get('registerTsNode', true), 11 | disablePseudoTerminal: config.get('disablePseudoTerminal', false), 12 | inlineTensorflowVisualizations: config.get('node_notebook.inlineTensorflowVisualizations', true), 13 | injectTsVis: false, 14 | injectPlotly: false, 15 | terminalColumns: 80, 16 | terminalRows: 30 17 | }; 18 | } 19 | export async function writeConfigurationToTempFile(): Promise { 20 | const config = getConfiguration(); 21 | const tmpFile = await new Promise<{ path: string; cleanupCallback: Function }>((resolve, reject) => { 22 | tmp.file({ postfix: '.json' }, (err, path, _, cleanupCallback) => { 23 | if (err) { 24 | return reject(err); 25 | } 26 | resolve({ path, cleanupCallback }); 27 | }); 28 | }); 29 | registerDisposable({ dispose: () => tmpFile.cleanupCallback() }); 30 | await fs.writeFile(tmpFile.path, JSON.stringify(config)); 31 | return tmpFile.path; 32 | } 33 | -------------------------------------------------------------------------------- /src/extension/const.ts: -------------------------------------------------------------------------------- 1 | export const notebookType = 'node-notebook'; 2 | export const iwnotebookType = 'interactive'; 3 | 4 | const writingToConsoleOutputCompletdMarker = 'd1786f7c-d2ed-4a27-bd8a-ce19f704d111'; 5 | 6 | export function createConsoleOutputCompletedMarker(requestId: string) { 7 | return `${writingToConsoleOutputCompletdMarker}-${requestId}`; 8 | } 9 | -------------------------------------------------------------------------------- /src/extension/content/index.ts: -------------------------------------------------------------------------------- 1 | export { ContentProvider } from "./provider"; 2 | -------------------------------------------------------------------------------- /src/extension/content/walkThrough.ts: -------------------------------------------------------------------------------- 1 | import { CancellationTokenSource, commands, ExtensionContext, QuickPickItem, window, workspace } from 'vscode'; 2 | import * as path from 'path'; 3 | import { promises as fs } from "fs"; 4 | import { notebookType } from '../const'; 5 | import { ContentProvider } from '.'; 6 | 7 | const samples: (QuickPickItem & { path: string; command: string })[] = []; 8 | // basics 9 | samples.push({ 10 | command: 'node.notebook.sample.basics.richOutput', 11 | label: 'Generate rich outputs in node.js', 12 | description: 'basics', 13 | path: path.join('resources', 'docs', 'basics', 'richOutput.nnb') 14 | }); 15 | samples.push({ 16 | command: 'node.notebook.sample.basics.shellScripts', 17 | label: 'Run shell scripts from within a notebook', 18 | description: 'basics', 19 | path: path.join('resources', 'docs', 'basics', 'shellScripts.nnb') 20 | }); 21 | // Tensorflow.js 22 | samples.push({ 23 | command: 'node.notebook.sample.tensorflow.sample', 24 | label: 'View tensorflow visualizations in node.js', 25 | description: 'tensorflow.js', 26 | path: path.join('resources', 'docs', 'tensorflow', 'visualizations.nnb') 27 | }); 28 | samples.push({ 29 | command: 'node.notebook.sample.tensorflow.tensorboard', 30 | label: 'Train MNIST in tensorflow.js with visulizations', 31 | description: 'tensorflow.js', 32 | path: path.join('resources', 'docs', 'tensorflow', 'mnist.nnb') 33 | }); 34 | samples.push({ 35 | command: 'node.notebook.sample.tensorflow.carprice', 36 | label: 'Non-linear regression model for used car price prediction', 37 | description: 'tensorflow.js', 38 | path: path.join('resources', 'docs', 'tensorflow', 'carPrice.nnb') 39 | }); 40 | // Plotly 41 | samples.push({ 42 | command: 'node.notebook.sample.plotly.generate', 43 | label: 'Generate and view Plotly plots', 44 | description: 'plotly', 45 | path: path.join('resources', 'docs', 'plotly', 'generate.nnb') 46 | }); 47 | samples.push({ 48 | command: 'node.notebook.sample.plotly.saveToFile', 49 | label: 'Save a plot directly to a file', 50 | description: 'plotly', 51 | path: path.join('resources', 'docs', 'plotly', 'saveToFile.nnb') 52 | }); 53 | samples.push({ 54 | command: 'node.notebook.sample.plotly.embedInDiv', 55 | label: 'Render plots in custom HTML elements', 56 | description: 'plotly', 57 | path: path.join('resources', 'docs', 'plotly', 'embedInDiv.nnb') 58 | }); 59 | // danfo.js 60 | samples.push({ 61 | command: 'node.notebook.sample.danfojs.htmlOutput', 62 | label: 'View dataframe and series as HTML tables', 63 | description: 'danfo.js', 64 | path: path.join('resources', 'docs', 'danfojs', 'htmlOutput.nnb') 65 | }); 66 | samples.push({ 67 | command: 'node.notebook.sample.danfojs.plots', 68 | label: 'Generate and view danfo.js plots', 69 | description: 'danfo.js', 70 | path: path.join('resources', 'docs', 'danfojs', 'plots.nnb') 71 | }); 72 | samples.push({ 73 | command: 'node.notebook.sample.danfojs.embedInDiv', 74 | label: 'Render plots in custom HTML elements', 75 | description: 'danfo.js', 76 | path: path.join('resources', 'docs', 'danfojs', 'embedInDiv.nnb') 77 | }); 78 | samples.push({ 79 | command: 'node.notebook.sample.basics.tips', 80 | label: 'Tips and tricks for improved development experience in node.js notebooks', 81 | description: 'basics', 82 | path: path.join('resources', 'docs', 'basics', 'tips.nnb') 83 | }); 84 | // arquero 85 | samples.push({ 86 | command: 'node.notebook.sample.arquero.htmlOutput', 87 | label: 'View tables as HTML tables', 88 | description: 'arquero', 89 | path: path.join('resources', 'docs', 'arquero', 'htmlOutput.nnb') 90 | }); 91 | 92 | export class Samples { 93 | public static regsiter(context: ExtensionContext) { 94 | context.subscriptions.push( 95 | commands.registerCommand('node.notebook.sample', async () => { 96 | const selection = await window.showQuickPick(samples, { 97 | matchOnDescription: true, 98 | title: 'Select a sample', 99 | matchOnDetail: true 100 | }); 101 | if (selection) { 102 | openSample(selection, context); 103 | } 104 | }) 105 | ); 106 | 107 | context.subscriptions.push( 108 | ...samples.map((item) => commands.registerCommand(item.command, async () => openSample(item, context))) 109 | ); 110 | } 111 | } 112 | 113 | async function openSample(selection: QuickPickItem & { path: string }, context: ExtensionContext) { 114 | const contents = await fs.readFile(path.join(context.extensionUri.fsPath, selection.path)); 115 | const nb = await new ContentProvider().deserializeNotebook(contents, new CancellationTokenSource().token); 116 | void workspace.openNotebookDocument(notebookType, nb); 117 | } 118 | -------------------------------------------------------------------------------- /src/extension/coreUtils.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid'; 2 | 3 | export function noop() { 4 | // 5 | } 6 | 7 | export function sleep(ms: number) { 8 | return new Promise((resolve) => setTimeout(resolve, ms)); 9 | } 10 | 11 | export function errorToJson(ex?: Partial): Error { 12 | return { 13 | message: ex?.message || '', 14 | name: ex?.name || '', 15 | stack: ex?.stack || '' 16 | }; 17 | } 18 | export function errorFromJson(ex?: Partial): Error { 19 | const error = new Error(ex?.message || 'unknown'); 20 | error.name = ex?.name || ''; 21 | error.stack = ex?.stack || ''; 22 | return error; 23 | } 24 | export function generateId() { 25 | return `x${uuid().replace(/-/g, '')}`; 26 | } 27 | //====================== 28 | // Deferred 29 | 30 | // eslint-disable-next-line @typescript-eslint/naming-convention 31 | export interface Deferred { 32 | readonly promise: Promise; 33 | readonly resolved: boolean; 34 | readonly rejected: boolean; 35 | readonly completed: boolean; 36 | resolve(value?: T | PromiseLike): void; 37 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 38 | reject(reason?: any): void; 39 | } 40 | 41 | class DeferredImpl implements Deferred { 42 | private _resolve!: (value: T | PromiseLike) => void; 43 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 44 | private _reject!: (reason?: any) => void; 45 | private _resolved = false; 46 | private _rejected = false; 47 | private _promise: Promise; 48 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 49 | constructor(private scope: any = null) { 50 | // eslint-disable-next-line 51 | this._promise = new Promise((res, rej) => { 52 | this._resolve = res; 53 | this._reject = rej; 54 | }); 55 | } 56 | public resolve(_value?: T | PromiseLike) { 57 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, prefer-rest-params 58 | this._resolve.apply(this.scope ? this.scope : this, arguments as any); 59 | this._resolved = true; 60 | } 61 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 62 | public reject(_reason?: any) { 63 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, prefer-rest-params 64 | this._reject.apply(this.scope ? this.scope : this, arguments as any); 65 | this._rejected = true; 66 | } 67 | get promise(): Promise { 68 | return this._promise; 69 | } 70 | get resolved(): boolean { 71 | return this._resolved; 72 | } 73 | get rejected(): boolean { 74 | return this._rejected; 75 | } 76 | get completed(): boolean { 77 | return this._rejected || this._resolved; 78 | } 79 | } 80 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 81 | export function createDeferred(scope: any = null): Deferred { 82 | return new DeferredImpl(scope); 83 | } 84 | 85 | export function createDeferredFrom(...promises: Promise[]): Deferred { 86 | const deferred = createDeferred(); 87 | Promise.all(promises) 88 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 89 | .then(deferred.resolve.bind(deferred) as any) 90 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 91 | .catch(deferred.reject.bind(deferred) as any); 92 | 93 | return deferred; 94 | } 95 | export function createDeferredFromPromise(promise: Promise): Deferred { 96 | const deferred = createDeferred(); 97 | promise.then(deferred.resolve.bind(deferred)).catch(deferred.reject.bind(deferred)); 98 | return deferred; 99 | } 100 | -------------------------------------------------------------------------------- /src/extension/index.ts: -------------------------------------------------------------------------------- 1 | import { ContentProvider } from './content'; 2 | import { registerDisposableRegistry } from './utils'; 3 | import { ExtensionContext } from 'vscode'; 4 | import { Controller } from './kernel'; 5 | import { ServerLogger } from './serverLogger'; 6 | import { TensorflowVisClient } from './tfjsvis'; 7 | import { DebuggerCommands } from './kernel/debugger/commands'; 8 | import { DebuggerFactory } from './kernel/debugger/debugFactory'; 9 | import { PlotlyDownloadRenderer } from './kernel/plotly'; 10 | import { ShellKernel } from './kernel/shellKernel'; 11 | import { JavaScriptKernel } from './kernel/jsKernel'; 12 | import { Compiler } from './kernel/compiler'; 13 | import { Samples } from './content/walkThrough'; 14 | import { NodeRepl } from './kernel/repl'; 15 | 16 | export async function activate(context: ExtensionContext) { 17 | registerDisposableRegistry(context); 18 | Samples.regsiter(context); 19 | Compiler.register(context); 20 | ContentProvider.register(context); 21 | Controller.regsiter(); 22 | ServerLogger.register(); 23 | TensorflowVisClient.register(context); 24 | DebuggerCommands.register(context); 25 | DebuggerFactory.regsiter(context); 26 | PlotlyDownloadRenderer.register(context); 27 | ShellKernel.register(context); 28 | JavaScriptKernel.register(context); 29 | NodeRepl.register(context); 30 | } 31 | -------------------------------------------------------------------------------- /src/extension/kernel/browserExecution.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import { CancellationToken, NotebookCellExecution, NotebookCellOutput, NotebookCellOutputItem } from 'vscode'; 3 | import { Compiler } from './compiler'; 4 | import { getNextExecutionOrder } from './executionOrder'; 5 | import { CellExecutionState } from './types'; 6 | 7 | export async function execute(task: NotebookCellExecution, _token: CancellationToken): Promise { 8 | task.start(Date.now()); 9 | void task.clearOutput(); 10 | task.executionOrder = getNextExecutionOrder(task.cell.notebook); 11 | if (task.cell.document.languageId === 'javascript' || task.cell.document.languageId === 'typescript') { 12 | const code = Compiler.getOrCreateCodeObject(task.cell); 13 | const script = ``; 14 | void task.appendOutput(new NotebookCellOutput([NotebookCellOutputItem.text(script, 'text/html')])); 15 | } else { 16 | void task.appendOutput( 17 | new NotebookCellOutput([NotebookCellOutputItem.text(task.cell.document.getText(), 'text/html')]) 18 | ); 19 | } 20 | task.end(true, Date.now()); 21 | return CellExecutionState.success; 22 | } 23 | -------------------------------------------------------------------------------- /src/extension/kernel/cellExecutionQueue.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CancellationToken, 3 | CancellationTokenSource, 4 | NotebookCell, 5 | NotebookCellExecution, 6 | NotebookCellKind, 7 | NotebookController, 8 | NotebookDocument 9 | } from 'vscode'; 10 | import { IDisposable } from '../types'; 11 | import { registerDisposable } from '../utils'; 12 | import { JavaScriptKernel } from './jsKernel'; 13 | import { ShellKernel } from './shellKernel'; 14 | import { execute as executeInBrowser } from './browserExecution'; 15 | import { CellExecutionState } from './types'; 16 | import { CellDiagnosticsProvider } from './problems'; 17 | import { CellOutput } from './cellOutput'; 18 | 19 | function wrapCancellationToken(token: CancellationToken): CancellationTokenSource { 20 | const wrapper = new CancellationTokenSource(); 21 | token.onCancellationRequested(() => { 22 | wrapper.cancel(); 23 | }); 24 | return wrapper; 25 | } 26 | const cellExecutionQueues = new WeakMap(); 27 | export class CellExecutionQueue implements IDisposable { 28 | private queue?: Promise; 29 | private pendingCells: { cell: NotebookCell; task: NotebookCellExecution; token: CancellationTokenSource }[] = []; 30 | private constructor( 31 | private readonly notebookDocument: NotebookDocument, 32 | private readonly controller: NotebookController 33 | ) { 34 | registerDisposable(this); 35 | } 36 | public static get(notebookDocument: NotebookDocument) { 37 | return cellExecutionQueues.get(notebookDocument); 38 | } 39 | public static create(notebookDocument: NotebookDocument, controller: NotebookController) { 40 | const queue = new CellExecutionQueue(notebookDocument, controller); 41 | cellExecutionQueues.set(notebookDocument, queue); 42 | return queue; 43 | } 44 | public dispose() { 45 | this.stop(); 46 | JavaScriptKernel.get(this.notebookDocument)?.dispose(); 47 | cellExecutionQueues.delete(this.notebookDocument); 48 | } 49 | public async enqueueAndRun(cell: NotebookCell) { 50 | if (this.pendingCells.some((item) => item.cell === cell)) { 51 | return; 52 | } 53 | // Ignore non-code cells. 54 | if (cell.kind === NotebookCellKind.Markup) { 55 | return; 56 | } 57 | // Nothing to do with empty cells. 58 | if (cell.document.getText().trim().length === 0) { 59 | return; 60 | } 61 | CellDiagnosticsProvider.clearErrors(cell.notebook); 62 | const task = this.controller.createNotebookCellExecution(cell); 63 | const token = wrapCancellationToken(task.token); 64 | this.pendingCells.push({ cell, task, token }); 65 | this.start(); 66 | } 67 | private stop() { 68 | this.pendingCells.forEach(({ task, token }) => { 69 | token.cancel(); 70 | // This can fall over, as we may have already cancelled this task. 71 | try { 72 | task.end(undefined); 73 | } catch {} 74 | }); 75 | this.pendingCells = []; 76 | this.queue = undefined; 77 | } 78 | private start() { 79 | if (this.queue) { 80 | return; 81 | } 82 | this.queue = Promise.resolve(); 83 | this.runCells(); 84 | } 85 | private runCells() { 86 | if (!this.queue) { 87 | return; 88 | } 89 | this.queue = this.queue 90 | .then(async () => { 91 | if (this.pendingCells.length === 0) { 92 | this.queue = undefined; 93 | return; 94 | } 95 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 96 | const { task, token } = this.pendingCells[0]; 97 | if (token.token.isCancellationRequested) { 98 | return; 99 | } 100 | try { 101 | const result = await this.runCell(task, token); 102 | // We should only remove this item after running the cell. 103 | // Else if we hit the stop/interrupt button, then the existing cells will not be aborted. 104 | this.pendingCells.shift(); 105 | if (result == CellExecutionState.error) { 106 | this.stop(); 107 | } 108 | } catch (ex) { 109 | console.error('Error in running cells', ex); 110 | this.stop(); 111 | } 112 | }) 113 | .finally(() => { 114 | if (this.pendingCells.length === 0) { 115 | this.queue = undefined; 116 | } else { 117 | this.runCells(); 118 | } 119 | }); 120 | } 121 | private async runCell(task: NotebookCellExecution, token: CancellationTokenSource): Promise { 122 | CellOutput.resetCell(task.cell); 123 | switch (task.cell.document.languageId) { 124 | case 'shellscript': 125 | case 'powershell': { 126 | return ShellKernel.execute(task, token.token); 127 | } 128 | case 'javascript': 129 | case 'typescript': { 130 | const kernel = JavaScriptKernel.getOrCreate(task.cell.notebook, this.controller); 131 | return kernel.runCell(task, token.token); 132 | } 133 | case 'html': { 134 | return executeInBrowser(task, token.token); 135 | } 136 | default: 137 | break; 138 | } 139 | return CellExecutionState.success; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/extension/kernel/controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | commands, 3 | NotebookCell, 4 | NotebookController, 5 | NotebookControllerAffinity, 6 | NotebookDocument, 7 | notebooks, 8 | Uri, 9 | workspace 10 | } from 'vscode'; 11 | import { iwnotebookType, notebookType } from '../const'; 12 | import { IDisposable } from '../types'; 13 | import { disposeAllDisposables, registerDisposable } from '../utils'; 14 | import { CellExecutionQueue } from './cellExecutionQueue'; 15 | import { CellOutput } from './cellOutput'; 16 | import { resetExecutionOrder } from './executionOrder'; 17 | import { JavaScriptKernel } from './jsKernel'; 18 | 19 | export function isBrowserController(controller: NotebookController) { 20 | return controller.id.includes('-browser-'); 21 | } 22 | export class Controller implements IDisposable { 23 | private static _tsNbController: NotebookController; 24 | private static _iwController: NotebookController; 25 | public static get nodeNotebookController(): NotebookController { 26 | return Controller._tsNbController; 27 | } 28 | public static get interactiveController(): NotebookController { 29 | return Controller._iwController; 30 | } 31 | private readonly disposables: IDisposable[] = []; 32 | public static regsiter() { 33 | registerDisposable(new Controller()); 34 | } 35 | constructor() { 36 | Controller._tsNbController = this.createController(notebookType, 'node'); 37 | Controller._iwController = this.createController(iwnotebookType, 'node'); 38 | workspace.onDidOpenNotebookDocument( 39 | (e) => { 40 | if (e.notebookType === notebookType) { 41 | Controller._tsNbController.updateNotebookAffinity(e, NotebookControllerAffinity.Preferred); 42 | } 43 | }, 44 | this, 45 | this.disposables 46 | ); 47 | workspace.onDidCloseNotebookDocument((e) => this.resetNotebook(e), this, this.disposables); 48 | this.disposables.push(commands.registerCommand('node.kernel.restart', this.restart, this)); 49 | } 50 | public dispose() { 51 | disposeAllDisposables(this.disposables); 52 | Controller._tsNbController.dispose(); 53 | Controller._iwController.dispose(); 54 | } 55 | private createController(nbType: string, type: 'node' | 'browser') { 56 | const controller = notebooks.createNotebookController( 57 | `controller-${type}-${nbType}`, 58 | nbType, 59 | type === 'node' ? 'Node.js' : 'JavaScript/TypeScript in Browser' 60 | ); 61 | if (type === 'node') { 62 | controller.description = ''; 63 | controller.detail = 'Execute & debug JavaScript/TypeScript in node.js'; 64 | controller.supportedLanguages = ['typescript', 'javascript', 'html', 'shellscript', 'powershell']; 65 | } else { 66 | controller.description = 'JavaScript/TypeScript Kernel running in Browser'; 67 | controller.detail = 'Support for JavaScript in Notebooks'; 68 | controller.supportedLanguages = ['typescript', 'javascript', 'html', 'shellscript', 'powershell']; 69 | } 70 | controller.executeHandler = this.executeHandler; 71 | controller.interruptHandler = this.interrupt; 72 | controller.supportsExecutionOrder = true; 73 | return controller; 74 | } 75 | private async executeHandler(cells: NotebookCell[], notebook: NotebookDocument, controller: NotebookController) { 76 | const queue = CellExecutionQueue.get(notebook) || CellExecutionQueue.create(notebook, controller); 77 | cells.forEach((cell) => queue.enqueueAndRun(cell)); 78 | } 79 | private interrupt(notebook: NotebookDocument) { 80 | CellExecutionQueue.get(notebook)?.dispose(); 81 | } 82 | private restart(uri?: Uri) { 83 | if (!uri) { 84 | return; 85 | } 86 | const notebook = workspace.notebookDocuments.find((item) => item.uri.toString() === uri?.toString()); 87 | if (!notebook) { 88 | return; 89 | } 90 | this.resetNotebook(notebook); 91 | } 92 | private resetNotebook(notebook: NotebookDocument) { 93 | resetExecutionOrder(notebook); 94 | CellExecutionQueue.get(notebook)?.dispose(); 95 | JavaScriptKernel.get(notebook)?.dispose(); 96 | CellOutput.reset(notebook); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/extension/kernel/debugger/commands.ts: -------------------------------------------------------------------------------- 1 | import { commands, ExtensionContext, NotebookCell, Uri, workspace } from 'vscode'; 2 | import { Controller } from '..'; 3 | import { CellExecutionQueue } from '../cellExecutionQueue'; 4 | import { JavaScriptKernel } from '../jsKernel'; 5 | import { DebuggerFactory } from './debugFactory'; 6 | 7 | export class DebuggerCommands { 8 | public static register(context: ExtensionContext) { 9 | context.subscriptions.push( 10 | commands.registerCommand('node.notebook.debug', async (uri: Uri) => startDebugger(uri)) 11 | ); 12 | context.subscriptions.push( 13 | commands.registerCommand('node.notebook.runAndDebugCell', async (cell: NotebookCell | undefined) => { 14 | if (!cell) { 15 | return; 16 | } 17 | try { 18 | await startDebugger(cell.notebook.uri); 19 | const queue = 20 | CellExecutionQueue.get(cell.notebook) || 21 | CellExecutionQueue.create(cell.notebook, Controller.nodeNotebookController); 22 | queue.enqueueAndRun(cell); 23 | } catch (error) { 24 | console.error(error); 25 | } 26 | }) 27 | ); 28 | } 29 | } 30 | 31 | async function startDebugger(uri: Uri) { 32 | const notebook = workspace.notebookDocuments.find((item) => item.uri.toString() === uri.toString()); 33 | if (!notebook) { 34 | throw new Error('Notebook not found'); 35 | } 36 | const controller = Controller.nodeNotebookController; 37 | const kernel = JavaScriptKernel.getOrCreate(notebook, controller); 38 | return DebuggerFactory.start(notebook, kernel); 39 | } 40 | -------------------------------------------------------------------------------- /src/extension/kernel/debugger/debugFactory.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { debug, NotebookDocument, Uri, ExtensionContext, workspace, DebugSession } from 'vscode'; 3 | import { createDeferred, Deferred } from '../../coreUtils'; 4 | import type { JavaScriptKernel } from '../jsKernel'; 5 | import { Debugger } from './debugger'; 6 | 7 | const debuggersByNotebookId = new Map< 8 | string, 9 | { 10 | notebook: NotebookDocument; 11 | kernel: JavaScriptKernel; 12 | debuggerAttached: Deferred; 13 | } 14 | >(); 15 | const debuggersByNotebook = new WeakMap(); 16 | const debuggersBySession = new WeakMap(); 17 | const debugTypes = ['node', 'node2', 'pwa-node', 'pwa-chrome']; 18 | export class DebuggerFactory { 19 | private static extensionDirectory: Uri; 20 | public static regsiter(context: ExtensionContext) { 21 | DebuggerFactory.extensionDirectory = context.extensionUri; 22 | DebuggerFactory.attachDebuggerTrackers(); 23 | workspace.onDidCloseNotebookDocument((e) => { 24 | const documentId = debuggersByNotebook.get(e); 25 | if (documentId) { 26 | debuggersByNotebookId.delete(documentId); 27 | } 28 | }); 29 | debug.onDidTerminateDebugSession((e) => { 30 | const documentId = debuggersBySession.get(e); 31 | debuggersBySession.delete(e); 32 | if (documentId) { 33 | debuggersByNotebookId.delete(documentId); 34 | } 35 | }); 36 | } 37 | public static isAttached(notebook: NotebookDocument) { 38 | return !!debuggersByNotebookId.get(notebook.uri.toString()); 39 | } 40 | public static stripDebuggerMessages(data: string): string { 41 | // When debugging we get messages of the form 42 | // Debugger listening on ws://127.0.0.1:60620/e2558def-1a2a-498a-861c-46a1f9eabd67 43 | // For help, see: https://nodejs.org/en/docs/inspector 44 | // Remove this. 45 | if (data.includes('Debugger listening on ws')) { 46 | const lines = data.split('\n'); 47 | const indexOfLineWithDebuggerMessage = lines.findIndex((line) => 48 | line.startsWith('Debugger listening on ws:') 49 | ); 50 | if (indexOfLineWithDebuggerMessage >= 0) { 51 | lines.splice(indexOfLineWithDebuggerMessage, 2); 52 | } 53 | data = lines.join('\n'); 54 | } 55 | // In case this message came separately. 56 | // For help, see: https://nodejs.org/en/docs/inspector 57 | if (data.includes('For help, see: https://nodejs.org/en/docs/inspector')) { 58 | const lines = data.split('\n'); 59 | const indexOfLineWithDebuggerMessage = lines.findIndex((line) => 60 | line.startsWith('For help, see: https://nodejs.org/en/docs/inspector') 61 | ); 62 | if (indexOfLineWithDebuggerMessage >= 0) { 63 | lines.splice(indexOfLineWithDebuggerMessage, 1); 64 | } 65 | data = lines.join('\n'); 66 | } 67 | return data; 68 | } 69 | public static async start(notebook: NotebookDocument, kernel: JavaScriptKernel) { 70 | let info = debuggersByNotebookId.get(notebook.uri.toString()); 71 | if (info) { 72 | return info.debuggerAttached.promise; 73 | } 74 | info = { 75 | notebook, 76 | kernel, 77 | debuggerAttached: createDeferred() 78 | }; 79 | debuggersByNotebookId.set(notebook.uri.toString(), info); 80 | info.debuggerAttached.promise.catch(() => { 81 | if (debuggersByNotebookId.get(notebook.uri.toString()) === info) { 82 | debuggersByNotebookId.delete(notebook.uri.toString()); 83 | } 84 | }); 85 | DebuggerFactory.startInternal(notebook, kernel); 86 | return info.debuggerAttached.promise; 87 | } 88 | private static async startInternal(notebook: NotebookDocument, kernel: JavaScriptKernel) { 89 | const port = await kernel.debugPort; 90 | const name = path.basename(notebook.uri.toString()); 91 | const folder = 92 | workspace.getWorkspaceFolder(notebook.uri) || 93 | (workspace.workspaceFolders?.length ? workspace.workspaceFolders[0] : undefined); 94 | const started = await debug.startDebugging(folder, { 95 | type: 'pwa-node', 96 | timeout: 100000, // Hmm... 97 | name: name, 98 | port: port, 99 | request: 'attach', 100 | __document: notebook.uri.toString(), 101 | sourceMaps: true, 102 | internalConsoleOptions: 'neverOpen', 103 | skipFiles: [ 104 | '/**', 105 | path.join(path.dirname(DebuggerFactory.extensionDirectory.fsPath), '**', '*.js') 106 | ] 107 | }); 108 | if (!started) { 109 | debuggersByNotebookId.delete(notebook.uri.toString()); 110 | throw new Error('Debugger failed to start'); 111 | } 112 | } 113 | 114 | private static attachDebuggerTrackers() { 115 | debugTypes.map((debugType) => { 116 | debug.registerDebugAdapterTrackerFactory(debugType, { 117 | createDebugAdapterTracker: (session: DebugSession) => { 118 | const __document: string | undefined = 119 | session.configuration.__document || session.parentSession?.configuration?.__document; 120 | const info = __document && debuggersByNotebookId.get(__document); 121 | if (!info || !__document) { 122 | return undefined; 123 | } 124 | // There are two debug sessions when debugging node.js 125 | // A wrapper and the real debug session. 126 | // Hence, remember the fact that we could have two debug trackers. 127 | const jsDebugger = new Debugger(info.notebook, session, info.kernel); 128 | jsDebugger.ready.then(() => info.debuggerAttached.resolve()); 129 | debuggersBySession.set(session, __document); 130 | debuggersByNotebook.set(info.notebook, __document); 131 | return jsDebugger; 132 | } 133 | }); 134 | }); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/extension/kernel/executionOrder.ts: -------------------------------------------------------------------------------- 1 | import { NotebookDocument } from 'vscode'; 2 | 3 | const executionOrders = new WeakMap(); 4 | export function resetExecutionOrder(notebook: NotebookDocument) { 5 | executionOrders.delete(notebook); 6 | } 7 | export function getNextExecutionOrder(notebook: NotebookDocument) { 8 | const nextExecutionOrder = (executionOrders.get(notebook) || 0) + 1; 9 | executionOrders.set(notebook, nextExecutionOrder); 10 | return nextExecutionOrder; 11 | } 12 | -------------------------------------------------------------------------------- /src/extension/kernel/index.ts: -------------------------------------------------------------------------------- 1 | export { Controller } from './controller'; 2 | -------------------------------------------------------------------------------- /src/extension/kernel/plotly.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, notebooks } from 'vscode'; 2 | import { ResponseType } from '../server/types'; 3 | import { JavaScriptKernel } from './jsKernel'; 4 | 5 | export class PlotlyDownloadRenderer { 6 | public static register(context: ExtensionContext) { 7 | notebooks 8 | .createRendererMessaging('node-notebook-plot-renderer') 9 | .onDidReceiveMessage(onDidReceiveMessage, this, context.subscriptions); 10 | } 11 | } 12 | 13 | function onDidReceiveMessage({ message }: { message?: ResponseType }) { 14 | if (message?.type === 'plotGenerated') { 15 | JavaScriptKernel.broadcast(message); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/extension/kernel/problems.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Diagnostic, 3 | DiagnosticSeverity, 4 | ExtensionContext, 5 | languages, 6 | NotebookDocument, 7 | Position, 8 | workspace 9 | } from 'vscode'; 10 | import { IDisposable } from '../types'; 11 | import { disposeAllDisposables } from '../utils'; 12 | import { parse as parseStack } from 'error-stack-parser'; 13 | import type ErrorStackParser from 'error-stack-parser'; 14 | import { Compiler } from './compiler'; 15 | 16 | const diagnosticsCollection = languages.createDiagnosticCollection('Typscript Notebook'); 17 | 18 | export class CellDiagnosticsProvider { 19 | private readonly disposables: IDisposable[] = []; 20 | public static register(context: ExtensionContext) { 21 | context.subscriptions.push(new CellDiagnosticsProvider()); 22 | context.subscriptions.push(workspace.onDidCloseNotebookDocument((e) => CellDiagnosticsProvider.clearErrors(e))); 23 | } 24 | public dispose() { 25 | diagnosticsCollection.dispose(); 26 | disposeAllDisposables(this.disposables); 27 | } 28 | 29 | public static clearErrors(notebook: NotebookDocument) { 30 | notebook.getCells().forEach((cell) => diagnosticsCollection.delete(cell.document.uri)); 31 | } 32 | public static displayErrorsAsProblems(notebook: NotebookDocument, ex?: Partial) { 33 | // At any point we can only have one execution that results in an error for a notebook. 34 | // Thus all of the old problems are no longer valid, hence clear everything for this notebook. 35 | CellDiagnosticsProvider.clearErrors(notebook); 36 | if (!ex || !ex?.stack) { 37 | return; 38 | } 39 | let stacks: ErrorStackParser.StackFrame[] = []; 40 | try { 41 | stacks = parseStack(ex as Error); 42 | } catch (ex) { 43 | // TODO: Seems to fail in windows. 44 | console.error('Failed to parse Stack trace', ex); 45 | return; 46 | } 47 | if (stacks.length === 0) { 48 | return; 49 | } 50 | // We're only interested in where the error is (not where a particular function was invoked from). 51 | // Hence take the top most stack. 52 | const stack = stacks[0]; 53 | const cell = stack.fileName && Compiler.getCellFromTemporaryPath(stack.fileName); 54 | if (!cell) { 55 | return; 56 | } 57 | const codeObject = Compiler.getCodeObject(cell); 58 | if (!codeObject) { 59 | return; 60 | } 61 | const sourceMap = Compiler.getSourceMapsInfo(codeObject); 62 | if (!sourceMap) { 63 | return; 64 | } 65 | const line = stack.lineNumber || 1; 66 | const column = stack.columnNumber || 1; 67 | const mappedLocation = Compiler.getMappedLocation(codeObject, { line, column }, 'DAPToVSCode'); 68 | if (typeof mappedLocation.column !== 'number' || typeof mappedLocation.line !== 'number') { 69 | return; 70 | } 71 | const position = new Position(mappedLocation.line - 1, mappedLocation.column); 72 | const wordRange = cell.document.getWordRangeAtPosition(position); 73 | if (!wordRange) { 74 | return; 75 | } 76 | const diagnostic = new Diagnostic(wordRange, ex.message || '', DiagnosticSeverity.Error); 77 | diagnosticsCollection.set(cell.document.uri, [diagnostic]); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/extension/kernel/repl.ts: -------------------------------------------------------------------------------- 1 | import { commands, ExtensionContext, ViewColumn } from 'vscode'; 2 | import { Controller } from '.'; 3 | 4 | export class NodeRepl { 5 | public static register(context: ExtensionContext) { 6 | context.subscriptions.push( 7 | commands.registerCommand('node.notebook.newREPL', async () => { 8 | await commands.executeCommand( 9 | 'interactive.open', 10 | { viewColumn: ViewColumn.Active, preserveFocus: false }, 11 | undefined, 12 | Controller.interactiveController.id, 13 | 'Node.js REPL' 14 | ); 15 | await commands.executeCommand('notebook.selectKernel', { 16 | id: Controller.interactiveController.id, 17 | extension: 'donjayamanne.typescript-notebook' 18 | }); 19 | }) 20 | ); 21 | } 22 | constructor() {} 23 | } 24 | -------------------------------------------------------------------------------- /src/extension/kernel/types.ts: -------------------------------------------------------------------------------- 1 | export enum CellExecutionState { 2 | notExecutedEmptyCell = 'notExecutedEmptyCell', 3 | success = 'success', 4 | error = 'error' 5 | } 6 | -------------------------------------------------------------------------------- /src/extension/serializer.ts: -------------------------------------------------------------------------------- 1 | // https://gist.github.com/jonathanlurie/04fa6343e64f750d03072ac92584b5df 2 | /* 3 | Author: Jonathan Lurie - http://me.jonathanlurie.fr 4 | License: MIT 5 | 6 | The point of this little gist is to fix the issue of losing 7 | typed arrays when calling the default JSON serilization. 8 | The default mode has for effect to convert typed arrays into 9 | object like that: {0: 0.1, 1: 0.2, 2: 0.3} what used to be 10 | Float32Array([0.1, 0.2, 0.3]) and once it takes the shape of an 11 | object, there is no way to get it back in an automated way! 12 | 13 | The fix leverages the usually-forgotten functions that can be 14 | called as arguments of JSON.stringify and JSON.parse: the 15 | replacer and the reviver. 16 | */ 17 | 18 | // get the glogal context for compatibility with node and browser 19 | const context = typeof window === 'undefined' ? global : window; 20 | 21 | // flag that will be sliped in the json string 22 | const FLAG_TYPED_ARRAY = 'FLAG_TYPED_ARRAY'; 23 | 24 | // eslint-disable-next-line @typescript-eslint/ban-types 25 | export function serialize(obj: {}): string { 26 | return JSON.stringify(obj, function (key, value) { 27 | // the replacer function is looking for some typed arrays. 28 | // If found, it replaces it by a trio 29 | if ( 30 | value instanceof Int8Array || 31 | value instanceof Uint8Array || 32 | value instanceof Uint8ClampedArray || 33 | value instanceof Int16Array || 34 | value instanceof Uint16Array || 35 | value instanceof Int32Array || 36 | value instanceof Uint32Array || 37 | value instanceof Float32Array || 38 | value instanceof Float64Array 39 | ) { 40 | const replacement = { 41 | constructor: value.constructor.name, 42 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 43 | data: Array.apply([], value as any), 44 | flag: FLAG_TYPED_ARRAY 45 | }; 46 | return replacement; 47 | } 48 | return value; 49 | }); 50 | } 51 | export function deserialize(jsonStr: string) { 52 | return JSON.parse(jsonStr, function (key, value) { 53 | // the reviver function looks for the typed array flag 54 | try { 55 | if ('flag' in value && value.flag === FLAG_TYPED_ARRAY) { 56 | // if found, we convert it back to a typed array 57 | return new context[value.constructor](value.data); 58 | } 59 | } catch (e) { 60 | // 61 | } 62 | 63 | // if flag not found no conversion is done 64 | return value; 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /src/extension/server/comms.ts: -------------------------------------------------------------------------------- 1 | import { RequestType, ResponseType } from './types'; 2 | import * as WebSocket from 'ws'; 3 | import { EventEmitter } from 'events'; 4 | import { createDeferred } from '../coreUtils'; 5 | import { format } from 'util'; 6 | const stringify = require('json-stringify-safe'); 7 | 8 | const ws = createDeferred(); 9 | const messagesToSend: ResponseType[] = []; 10 | export function initializeComms(websocket: WebSocket) { 11 | ws.resolve(websocket); 12 | } 13 | export function sendMessage(message: ResponseType) { 14 | messagesToSend.push(message); 15 | ws.promise.then((websocket) => { 16 | while (messagesToSend.length) { 17 | const message = messagesToSend.shift(); 18 | if (!message) { 19 | continue; 20 | } 21 | try { 22 | websocket.send(stringify(message)); 23 | } catch (ex) { 24 | sendMessage({ 25 | type: 'logMessage', 26 | requestId: '', 27 | category: 'error', 28 | message: format(`Failed to send a message ${message.type}`, ex) 29 | }); 30 | } 31 | } 32 | }); 33 | } 34 | 35 | export const emitter = new EventEmitter(); 36 | export function addMessageHandler(type: RequestType['type'], listener: (message: any) => void) { 37 | emitter.on(`onMessage_${type}`, listener); 38 | } 39 | export function removeMessageHandler(type: RequestType['type'], listener: (message: any) => void) { 40 | emitter.off(`onMessage_${type}`, listener); 41 | } 42 | -------------------------------------------------------------------------------- /src/extension/server/extensions/arqueroFormatter.ts: -------------------------------------------------------------------------------- 1 | export class ArqueroFormatter { 2 | private static arqueroTable?: any; 3 | public static isArqueroTable(table: any) { 4 | try { 5 | const proto = table.__proto__ || table.prototype; 6 | return proto && ArqueroFormatter.arqueroTable === proto; 7 | } catch { 8 | // 9 | } 10 | return false; 11 | } 12 | public static initialize(arquero: typeof import('arquero')) { 13 | var table = arquero.table({ colA: ['a', 'b', 'c'], colB: [3, 4, 5] }); 14 | const proto = (table as any).__proto__ || (table as any).protptype; 15 | if (!proto) { 16 | return; 17 | } 18 | ArqueroFormatter.arqueroTable = proto; 19 | proto.print = function (opts) { 20 | const { display } = require('node-kernel'); 21 | display.html(this.toHTML(opts)); 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/extension/server/extensions/format.ts: -------------------------------------------------------------------------------- 1 | import * as kindOf from 'kind-of'; 2 | import * as fileType from 'file-type'; 3 | import * as util from 'util'; 4 | import { promises as fs } from "fs"; 5 | import { logMessage } from '../logger'; 6 | import { DisplayData } from '../types'; 7 | import { DanfoJsFormatter } from './danfoFormatter'; 8 | import { formatTensor, isTensor } from './tensorFormatter'; 9 | import { ArqueroFormatter } from './arqueroFormatter'; 10 | 11 | export function isBase64OrSvg(value: string) { 12 | return value.startsWith('data:image/') || (value.endsWith('') && value.includes(' { 21 | if (typeof image !== 'string') { 22 | return formatValue(image, requestId); 23 | } 24 | if (typeof image === 'string' && isBase64OrSvg(image)) { 25 | return formatValue(image, requestId); 26 | } 27 | // Sometimes the value could be a file path. 28 | try { 29 | const buffer = await fs.readFile(image); 30 | const type = await fileType.fromBuffer(buffer); 31 | if (type?.mime.startsWith('image/')) { 32 | return formatValue(`data:${type.mime};base64,${buffer.toString('base64')}`, requestId); 33 | } 34 | } catch (ex) { 35 | logMessage('Unable to get image type', ex); 36 | } 37 | } 38 | export async function formatValue(value: unknown, requestId: string): Promise { 39 | if (typeof value === undefined) { 40 | return; 41 | } else if (ArqueroFormatter.isArqueroTable(value)) { 42 | return { 43 | type: 'html', 44 | requestId, 45 | value: (value as any).toHTML() 46 | }; 47 | } else if (typeof value === 'string' && value.startsWith('data:image/')) { 48 | return { 49 | type: 'multi-mime', 50 | requestId, 51 | value: [ 52 | { 53 | type: 'image', 54 | value: value.substring(value.indexOf(',') + 1), 55 | mime: value.substring(value.indexOf(':') + 1, value.indexOf(';')), 56 | requestId 57 | }, 58 | { 59 | type: 'text', 60 | value, 61 | requestId 62 | } 63 | ] 64 | }; 65 | } else if (typeof value === 'string' && value.endsWith('') && value.includes(' { 118 | const valueString = String(value); 119 | return valueString.replace(suffixRegex, ''); 120 | } 121 | }; 122 | } 123 | let colorRange; 124 | switch (options.colorMap) { 125 | case 'blues': 126 | colorRange = ['#f7fbff', '#4292c6']; 127 | break; 128 | case 'greyscale': 129 | colorRange = ['#000000', '#ffffff']; 130 | break; 131 | case 'viridis': 132 | default: 133 | colorRange = 'viridis'; 134 | break; 135 | } 136 | if (colorRange !== 'viridis') { 137 | //@ts-ignore 138 | const fill = spec.encoding.fill; 139 | // @ts-ignore 140 | fill.scale = { range: colorRange }; 141 | } 142 | if (options.domain) { 143 | //@ts-ignore 144 | const fill = spec.encoding.fill; 145 | // @ts-ignore 146 | if (fill.scale != null) { 147 | // @ts-ignore 148 | fill.scale = Object.assign({}, fill.scale, { domain: options.domain }); 149 | } else { 150 | // @ts-ignore 151 | fill.scale = { domain: options.domain }; 152 | } 153 | } 154 | return { spec, embedOpts }; 155 | } 156 | exports.heatmap = heatmap; 157 | async function convertToRowMajor(tf: typeof import('@tensorflow/tfjs-core'), inputValues) { 158 | let transposed; 159 | if (inputValues instanceof tf.Tensor) { 160 | transposed = inputValues.transpose(); 161 | } else { 162 | transposed = tf.tidy(() => tf.tensor2d(inputValues).transpose()); 163 | } 164 | // Download the intermediate tensor values and 165 | // dispose the transposed tensor. 166 | const transposedValues = await transposed.array(); 167 | transposed.dispose(); 168 | return transposedValues; 169 | } 170 | const defaultOpts = { 171 | height: null, 172 | width: null, 173 | xLabel: null, 174 | yLabel: null, 175 | xType: 'ordinal', 176 | yType: 'ordinal', 177 | colorMap: 'viridis', 178 | fontSize: 12, 179 | domain: null, 180 | rowMajor: false 181 | }; 182 | //# sourceMappingURL=heatmap.js.map 183 | -------------------------------------------------------------------------------- /src/extension/server/extensions/plotly.ts: -------------------------------------------------------------------------------- 1 | import type * as plotly from 'plotly.js'; 2 | import { promises as fs } from "fs"; 3 | import * as tmp from 'tmp'; 4 | import { addMessageHandler, removeMessageHandler, sendMessage } from '../comms'; 5 | import { ResponseType } from '../types'; 6 | import { errorFromJson } from '../../coreUtils'; 7 | 8 | export const Plotly = { 9 | requestId: '', 10 | async toBase64( 11 | data: plotly.Data[], 12 | layout: plotly.Layout, 13 | format: 'png' | 'svg' | 'jpeg' = 'png' 14 | ): Promise { 15 | const requestId = Plotly.requestId; 16 | // In case users forget and use the same args as newPlot 17 | if (typeof data === 'string') { 18 | data = layout as any; 19 | layout = format as any; 20 | format = 'png'; 21 | } 22 | return new Promise((resolve, reject) => { 23 | sendMessage({ 24 | type: 'output', 25 | requestId, 26 | data: { 27 | type: 'generatePlot', 28 | data, 29 | layout, 30 | requestId, 31 | download: true, 32 | format, 33 | hidden: true 34 | } 35 | }); 36 | const messageHandler = (data: ResponseType) => { 37 | if (data.type === 'plotGenerated' && data.requestId === requestId) { 38 | removeMessageHandler('plotGenerated', messageHandler); 39 | if (data.success) { 40 | resolve(data.base64); 41 | } else { 42 | reject(errorFromJson(data.error)); 43 | } 44 | } 45 | }; 46 | addMessageHandler('plotGenerated', messageHandler); 47 | }); 48 | }, 49 | async toFile( 50 | data: plotly.Data[], 51 | layout: plotly.Layout, 52 | format: 'png' | 'svg' | 'jpeg' = 'png', 53 | file?: string 54 | ): Promise { 55 | const base64 = await Plotly.toBase64(data, layout, format); 56 | file = 57 | file || 58 | (await new Promise((resolve, reject) => { 59 | tmp.file({ postfix: `.${format || 'png'}` }, (err, path, _, cleanupCallback) => { 60 | if (err) { 61 | return reject(err); 62 | } 63 | resolve(path); 64 | }); 65 | })); 66 | await fs.writeFile(file, Buffer.from(base64.substring(base64.indexOf(',') + 1), 'base64')); 67 | return file; 68 | }, 69 | async newPlot(ele: string, data: plotly.Data[], layout: plotly.Layout): Promise { 70 | sendMessage({ 71 | type: 'output', 72 | requestId: Plotly.requestId, 73 | data: { 74 | requestId: Plotly.requestId, 75 | type: 'generatePlot', 76 | ele, 77 | data, 78 | layout 79 | } 80 | }); 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /src/extension/server/extensions/readLineProxy.ts: -------------------------------------------------------------------------------- 1 | import { addMessageHandler, removeMessageHandler, sendMessage } from '../comms'; 2 | import { v4 as uuid } from 'uuid'; 3 | import { ReadLineQuestionResponse } from '../types'; 4 | 5 | export class ReadLineProxy { 6 | static initialize(readLine: typeof import('readline')) { 7 | const originalCreateInterface = readLine.createInterface; 8 | readLine.createInterface = function () { 9 | const rlInterface = originalCreateInterface.apply(readLine, arguments as any); 10 | const originalQuesttion = rlInterface.question; 11 | rlInterface.question = function (query: string) { 12 | const questionArgs = Array.prototype.slice.call(arguments); 13 | const callback = questionArgs.find((item) => typeof item === 'function'); 14 | if (callback) { 15 | const requestId = uuid(); 16 | sendMessage({ 17 | type: 'readlineRequest', 18 | question: query, 19 | requestId 20 | }); 21 | function callbackHandler(message: ReadLineQuestionResponse) { 22 | if (message.requestId === requestId) { 23 | removeMessageHandler('readlineResponse', callbackHandler); 24 | callback(message.answer); 25 | } 26 | } 27 | addMessageHandler('readlineResponse', callbackHandler); 28 | } 29 | return originalQuesttion.apply(rlInterface, arguments as any); 30 | }; 31 | 32 | return rlInterface; 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/extension/server/extensions/tensorFormatter.ts: -------------------------------------------------------------------------------- 1 | import type * as tfTypes from '@tensorflow/tfjs'; 2 | import { DisplayData } from '../types'; 3 | // import { Container } from '@tensorflow/tfjs-layers/dist/engine/container'; 4 | // import { Layer, Node } from '@tensorflow/tfjs-layers/dist/engine//topology'; 5 | 6 | export function isTensor(tensor: unknown) { 7 | return tensor && typeof tensor === 'object' && tensor.constructor.name === 'Tensor'; 8 | } 9 | 10 | export function formatTensor(data: unknown, requestId: string): DisplayData { 11 | const value = data as tfTypes.Tensor; 12 | return { 13 | type: 'multi-mime', 14 | requestId, 15 | value: [ 16 | { 17 | type: 'html', 18 | requestId, 19 | value: `
${value.toString()}
` 20 | }, 21 | { 22 | type: 'json', 23 | requestId, 24 | value 25 | } 26 | ] 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/extension/server/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import * as yargs from 'yargs'; 3 | import * as WebSocket from 'ws'; 4 | import * as fs from 'fs'; 5 | import { logErrorMessage, logMessage } from './logger'; 6 | import { execCode, initialize } from './codeExecution'; 7 | import { Configuration, RequestType } from './types'; 8 | import { emitter, initializeComms, sendMessage } from './comms'; 9 | import { createDeferred } from '../coreUtils'; 10 | 11 | const ws = createDeferred(); 12 | 13 | const argv = yargs(process.argv).argv; 14 | logMessage(`Started ${argv}`); 15 | const port = 'port' in argv ? (argv.port as number) : 0; 16 | const config = 'config' in argv ? (argv.config as string) : undefined; 17 | let configuration: Configuration | undefined; 18 | try { 19 | if (config) { 20 | configuration = JSON.parse(fs.readFileSync(config).toString()); 21 | } 22 | } catch (ex) { 23 | logErrorMessage(`Failed to read & parse configuration file ${config}`, ex); 24 | } 25 | 26 | initialize(configuration); 27 | 28 | if (!port) { 29 | console.error(`Port not provided, got ${JSON.stringify(argv)}`); 30 | logErrorMessage(`Port not provided, got ${JSON.stringify(argv)}`); 31 | process.exit(1); 32 | } 33 | function connectToServer(port: number) { 34 | const url = `ws://localhost:${port}`; 35 | const connection = new WebSocket(url); 36 | logMessage('connecting'); 37 | connection.on('open', () => { 38 | logMessage('initialized'); 39 | ws.resolve(connection); 40 | initializeComms(connection); 41 | }); 42 | 43 | connection.on('error', (error) => { 44 | logMessage('Error', error); 45 | logErrorMessage('WebSocket error', error); 46 | }); 47 | 48 | connection.on('message', (e) => { 49 | try { 50 | if (typeof e === 'string') { 51 | const data: RequestType = JSON.parse(e); 52 | logMessage(`Kernel got message ${data.type}`); 53 | switch (data.type) { 54 | case 'initialize': 55 | sendMessage({ type: 'initialized', requestId: '' }); 56 | break; 57 | case 'ping': { 58 | sendMessage({ type: 'pong', requestId: '' }); 59 | break; 60 | } 61 | case 'cellExec': { 62 | void execCode(data); 63 | break; 64 | } 65 | default: 66 | emitter.emit(`onMessage_${data.type}`, data); 67 | logErrorMessage(`Unknown message ${data['type']}`); 68 | break; 69 | } 70 | } 71 | } catch (ex) { 72 | logErrorMessage(`Error handling message ${e}`, ex); 73 | } 74 | }); 75 | } 76 | 77 | connectToServer(port); 78 | -------------------------------------------------------------------------------- /src/extension/server/logger.ts: -------------------------------------------------------------------------------- 1 | import * as util from 'util'; 2 | import { LogMessage } from './types'; 3 | import { sendMessage } from './comms'; 4 | 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | export function logErrorMessage(...param: any[]) { 7 | log('error', ...param); 8 | } 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | export function logMessage(...param: any[]) { 11 | log('info', ...param); 12 | } 13 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 14 | export function log(category: 'info' | 'error', ...param: any[]) { 15 | const msg: LogMessage = { 16 | type: 'logMessage', 17 | requestId: '', 18 | category, 19 | message: util.format(param[0], ...param.slice(1)) 20 | }; 21 | sendMessage(msg); 22 | } 23 | -------------------------------------------------------------------------------- /src/extension/server/magics/base.ts: -------------------------------------------------------------------------------- 1 | import { CodeObject } from '../types'; 2 | import * as repl from 'repl'; 3 | 4 | export abstract class MagicCommandHandler { 5 | abstract isMagicCommand(code: CodeObject): boolean; 6 | abstract handleCommand(coode: CodeObject, replServer: repl.REPLServer): Promise; 7 | } 8 | -------------------------------------------------------------------------------- /src/extension/server/magics/variables.ts: -------------------------------------------------------------------------------- 1 | import { sendMessage } from '../comms'; 2 | import { RunCellRequest, RunCellResponse } from '../types'; 3 | import * as repl from 'repl'; 4 | 5 | const reservedVars = new Set([ 6 | 'global', 7 | 'clearInterval', 8 | 'clearTimeout', 9 | 'setInterval', 10 | 'setTimeout', 11 | 'queueMicrotask', 12 | 'clearImmediate', 13 | 'setImmediate' 14 | ]); 15 | 16 | export class VariableListingMagicCommandHandler { 17 | public isMagicCommand(request: RunCellRequest): boolean { 18 | if (!request.code.code.includes('%who')) { 19 | return false; 20 | } 21 | return request.code.code 22 | .trim() 23 | .split(/\r?\n/) 24 | .map((line) => line.trim()) 25 | .filter((line) => !line.startsWith('#')) 26 | .some((line) => line.toLowerCase().startsWith('%who')); 27 | } 28 | public async handleCommand(request: RunCellRequest, replServer: repl.REPLServer) { 29 | const start = Date.now(); 30 | const vars = Object.keys(replServer.context); 31 | const execResult: RunCellResponse = { 32 | requestId: request.requestId, 33 | success: true, 34 | result: { 35 | type: 'text', 36 | requestId: request.requestId, 37 | value: vars 38 | .filter((item) => !reservedVars.has(item)) 39 | .sort() 40 | .map((name) => `${name} (type '${typeof replServer.context[name]}')`) 41 | .join('\n') 42 | }, 43 | type: 'cellExec', 44 | start, 45 | end: Date.now() 46 | }; 47 | sendMessage(execResult); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/extension/server/tsnode.ts: -------------------------------------------------------------------------------- 1 | import * as vm from 'vm'; 2 | import * as path from 'path'; 3 | 4 | const tsNodePath = path.join(__dirname, '..', '..', '..', 'resources', 'scripts', 'node_modules', 'ts-node'); 5 | export function register(context: vm.Context) { 6 | vm.runInNewContext(`require('${tsNodePath.replace(/\\/g, '/')}').register()`, context, { 7 | displayErrors: false 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /src/extension/serverLogger.ts: -------------------------------------------------------------------------------- 1 | import { OutputChannel, window } from 'vscode'; 2 | import { IDisposable } from './types'; 3 | import { registerDisposable } from './utils'; 4 | import * as util from 'util'; 5 | 6 | export class ServerLogger implements IDisposable { 7 | private static output: OutputChannel; 8 | constructor() { 9 | ServerLogger.output = window.createOutputChannel('TypeScript Kernel'); 10 | } 11 | public static register() { 12 | registerDisposable(new ServerLogger()); 13 | } 14 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 15 | public static appendLine(...params: any[]) { 16 | const message = util.format(params[0], ...params.slice(1)); 17 | ServerLogger.output.appendLine(message); 18 | } 19 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 20 | public static append(...params: any[]) { 21 | const message = util.format(params[0], ...params.slice(1)); 22 | ServerLogger.output.append(message); 23 | } 24 | 25 | public dispose() { 26 | ServerLogger.output.dispose(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/extension/tfjsvis/index.ts: -------------------------------------------------------------------------------- 1 | import { commands, ExtensionContext, Uri, Webview, WebviewView, WebviewViewProvider, window } from 'vscode'; 2 | import { TensorFlowVis } from '../server/types'; 3 | import { registerDisposable } from '../utils'; 4 | 5 | const viewType = 'tfjs-vis'; 6 | function shouldShowPanel(request: TensorFlowVis['request']) { 7 | switch (request) { 8 | case 'registerfitcallback': 9 | case 'fitcallback': 10 | return false; 11 | default: 12 | return true; 13 | } 14 | } 15 | export class TensorflowVisClient implements WebviewViewProvider { 16 | private static view?: WebviewView; 17 | private static cachedMessages: TensorFlowVis[] = []; 18 | private static viewVisibilitySet?: boolean; 19 | 20 | constructor(private readonly extensionUri: Uri) {} 21 | public static sendMessage(message: TensorFlowVis) { 22 | TensorflowVisClient.sendMessageInternal(message); 23 | } 24 | private static async sendMessageInternal(message: TensorFlowVis) { 25 | if (!TensorflowVisClient.view && shouldShowPanel(message.request)) { 26 | void commands.executeCommand('setContext', 'node_notebook.tfjs-vis.used', true); 27 | await commands.executeCommand(`${viewType}.focus`); 28 | } 29 | if (shouldShowPanel(message.request) && TensorflowVisClient.view) { 30 | if (!TensorflowVisClient.view.visible) { 31 | TensorflowVisClient.view.show(true); 32 | } 33 | } 34 | 35 | TensorflowVisClient.cachedMessages.push(message); 36 | TensorflowVisClient.sendMessages(); 37 | } 38 | private static sendMessages() { 39 | if (!TensorflowVisClient.view || !TensorflowVisClient.viewVisibilitySet) { 40 | return; 41 | } 42 | while (TensorflowVisClient.cachedMessages.length) { 43 | const message = TensorflowVisClient.cachedMessages.shift(); 44 | if (!message) { 45 | continue; 46 | } 47 | if (shouldShowPanel(message.request)) { 48 | if (!TensorflowVisClient.view.visible) { 49 | TensorflowVisClient.view.show(true); 50 | } 51 | } 52 | 53 | TensorflowVisClient.view.webview.postMessage(message); 54 | } 55 | } 56 | public static register(context: ExtensionContext) { 57 | const provider = new TensorflowVisClient(context.extensionUri); 58 | registerDisposable(window.registerWebviewViewProvider(viewType, provider)); 59 | } 60 | public resolveWebviewView(webviewView: WebviewView) { 61 | TensorflowVisClient.view = webviewView; 62 | 63 | webviewView.webview.options = { 64 | // Allow scripts in the webview 65 | enableScripts: true, 66 | localResourceRoots: [this.extensionUri] 67 | }; 68 | 69 | webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); 70 | webviewView.onDidDispose(() => { 71 | if (TensorflowVisClient.view === webviewView) { 72 | TensorflowVisClient.view = undefined; 73 | } 74 | }); 75 | 76 | webviewView.webview.onDidReceiveMessage((data) => { 77 | switch (data.type) { 78 | case 'loaded': { 79 | TensorflowVisClient.viewVisibilitySet = true; 80 | TensorflowVisClient.sendMessages(); 81 | 82 | break; 83 | } 84 | case 'tensorFlowVis': { 85 | // Messages that need to be sent back to the kernel 86 | break; 87 | } 88 | } 89 | }); 90 | } 91 | 92 | private _getHtmlForWebview(webview: Webview) { 93 | // Get the local path to main script run in the webview, then convert it to a uri we can use in the webview. 94 | const scriptUri = webview.asWebviewUri(Uri.joinPath(this.extensionUri, 'out', 'views', 'tfjsvis.js')); 95 | // Use a nonce to only allow a specific script to be run. 96 | const nonce = getNonce(); 97 | 98 | return ` 99 | 100 | 101 | 102 | 106 | 107 | 108 | Tensorflow Visualization 109 | 110 | 111 | 112 |
113 | 114 | `; 115 | } 116 | } 117 | 118 | function getNonce() { 119 | let text = ''; 120 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 121 | for (let i = 0; i < 32; i++) { 122 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 123 | } 124 | return text; 125 | } 126 | -------------------------------------------------------------------------------- /src/extension/types.ts: -------------------------------------------------------------------------------- 1 | export type IDisposable = { 2 | dispose: () => void; 3 | }; 4 | 5 | /** 6 | * Minko Gechev 7 | * In TypeScript, you can use Readonly to prevent the reassignment of properties of an object. Using conditional types, you can implement your own DeepReadonly with the snippet below! 8 | */ 9 | type DeepReadonlyArray = ReadonlyArray>; 10 | 11 | type DeepReadonlyObject = { 12 | readonly [P in keyof T]: DeepReadonly; 13 | }; 14 | 15 | export type DeepReadonly = T extends (infer R)[] 16 | ? DeepReadonlyArray 17 | : // eslint-disable-next-line @typescript-eslint/ban-types 18 | T extends Function 19 | ? T 20 | : // eslint-disable-next-line @typescript-eslint/ban-types 21 | T extends object 22 | ? DeepReadonlyObject 23 | : T; 24 | 25 | export type ReadWrite = { 26 | -readonly [P in keyof T]: T[P]; 27 | }; 28 | -------------------------------------------------------------------------------- /src/extension/utils.ts: -------------------------------------------------------------------------------- 1 | import { IDisposable } from './types'; 2 | import { ExtensionContext, NotebookDocument, workspace } from 'vscode'; 3 | import * as path from 'path'; 4 | 5 | const disposables: IDisposable[] = []; 6 | export function registerDisposableRegistry(context: ExtensionContext) { 7 | context.subscriptions.push({ 8 | dispose: () => disposeAllDisposables(disposables) 9 | }); 10 | } 11 | 12 | export function disposeAllDisposables(disposables: IDisposable[]) { 13 | while (disposables.length) { 14 | const item = disposables.shift(); 15 | if (item) { 16 | try { 17 | item.dispose(); 18 | } catch { 19 | // Noop. 20 | } 21 | } 22 | } 23 | } 24 | 25 | export function registerDisposable(disposable: IDisposable) { 26 | disposables.push(disposable); 27 | } 28 | export function getNotebookCwd(notebook: NotebookDocument) { 29 | if (notebook.isUntitled) { 30 | if (!workspace.workspaceFolders || workspace.workspaceFolders?.length === 0) { 31 | return; 32 | } 33 | return workspace.workspaceFolders[0].uri.fsPath; 34 | } 35 | return path.dirname(notebook.uri.fsPath); 36 | } 37 | -------------------------------------------------------------------------------- /src/node-kernel/README.md: -------------------------------------------------------------------------------- 1 | ## node-kernel 2 | 3 | This module contains helper functions for [node.js notebooks in Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=donjayamanne.typescript-notebook). 4 | 5 | This module unlocks the power of Notebooks in `Visual Studio Code` to provide rich outputs in node.js (i.e. you are no longer limited to plain text (`console.log`) outputs in node.js). 6 | 7 | Tip: This npm package should be installed only if you require code completion within the node notebooks in Visual Studio Code (i.e. get intellisense for `node-kernel` when using [node.js notebooks in Visual Studio Code]((https://marketplace.visualstudio.com/items?itemName=donjayamanne.typescript-notebook))). 8 | 9 | ## Usage 10 | 11 | ### Plain text, markdown, json output 12 | 13 | ```typescript 14 | const { display } = require('node-kernel'); 15 | 16 | // display plain text. 17 | display.text('Plain text'); 18 | 19 | // display markdown. 20 | display.markdown('Plain text'); 21 | 22 | // display markdown. 23 | display.json({ data: 'some json' }); 24 | ``` 25 | 26 | ### Html, Javascript output 27 | 28 | ```javascript 29 | // display html. 30 | display.html('

Hello

'); 31 | 32 | // Or even more some interactive HTML. 33 | const {display} = require('node-kernel'); 34 | const buttonText = 'Click me'; 35 | display.html(` 36 | 37 | 43 | `); 44 | 45 | // Include styles in HTML 46 | const {display} = require('node-kernel'); 47 | display.html(` 48 | 54 | 55 | `); 56 | 57 | // Render output that will result in execution of javascript in the output. 58 | display.javascript(` 59 | document.getElementById('myButton').innerHTML = 'Updated button'; 60 | `); 61 | ``` 62 | 63 | ### Images output 64 | 65 | ```javascript 66 | const {display} = require('node-kernel'); 67 | 68 | // Render a base64 encoded string as an image 69 | display.image('data:image/png;base64,iVBORw0KGgoAAAANSUhEU...'); 70 | // Render an svg string as an image 71 | display.image(', 90 | config?: Partial 91 | ): Promise; 92 | ``` 93 | 94 | ```javascript 95 | // Generate Plots 96 | const { Plotly } = require('node-kernel'); 97 | var data = [{ 98 | values: [19, 26, 55], 99 | labels: ['Residential', 'Non-Residential', 'Utility'], 100 | type: 'pie' 101 | }]; 102 | var layout = { 103 | height: 400, 104 | width: 500 105 | }; 106 | // If an HTML element named `myDiv` does not exist, the plot will be generated immediately below the cell. 107 | Plotly.newPlot('myDiv', data, layout); 108 | ``` 109 | 110 | 111 | ### Plots (plotly): Generate base64 string of the plot (useful if you want to embed this somewhere) 112 | 113 | ```typescript 114 | /** 115 | * Returns a base64 encoded string representation of the generated plot. 116 | * @param {plotly.Data[]} data See detailed documentation here https://plotly.com/javascript/plotlyjs-function-reference/#plotlynewplot 117 | * @param {plotly.Layout} layout See detailed documentation here https://plotly.com/javascript/plotlyjs-function-reference/#plotlynewplot 118 | * @param {('png' | 'svg' | 'jpeg')} [format] Defaults to 'png' if not specified. 119 | */ 120 | export function toBase64( 121 | data: plotly.Data[], 122 | layout: plotly.Layout, 123 | format?: 'png' | 'svg' | 'jpeg' 124 | ): Promise; 125 | ``` 126 | 127 | 128 | ### Plots (plotly): Generate plot and save to a file. 129 | 130 | ```typescript 131 | /** 132 | * Saves the generated plot into a file. 133 | * Return the path to the file name (if a file path is not provided a temporary image file is created and returned). 134 | * @param {plotly.Data[]} data See detailed documentation here https://plotly.com/javascript/plotlyjs-function-reference/#plotlynewplot 135 | * @param {plotly.Layout} layout See detailed documentation here https://plotly.com/javascript/plotlyjs-function-reference/#plotlynewplot 136 | * @param {('png' | 'svg' | 'jpeg')} [format] Defaults to 'png' if not specified. 137 | * @param {string} [file] Destination file path for the image to be downloaded. 138 | * If not specified, the image is downloaded into a temporary file and that path is returned. 139 | * @return {*} {Promise} 140 | */ 141 | export function toFile( 142 | data: plotly.Data[], 143 | layout: plotly.Layout, 144 | format?: 'png' | 'svg' | 'jpeg', 145 | file?: string 146 | ): Promise; 147 | ``` 148 | -------------------------------------------------------------------------------- /src/node-kernel/index.d.ts: -------------------------------------------------------------------------------- 1 | import type * as plotly from './plotly.js/index'; 2 | export namespace display { 3 | /** 4 | * Displays a base64 encoded image. 5 | */ 6 | export function image(base64: string): void; 7 | /** 8 | * Displays an SVG. 9 | */ 10 | export function image(svg: string): void; 11 | /** 12 | * Displays an image from file. 13 | */ 14 | export function image(filePath: string): void; 15 | /** 16 | * Displays an image from bytes. 17 | */ 18 | export function image(bytes: Uint8Array): void; 19 | 20 | /** 21 | * Displays a JSON 22 | */ 23 | export function json(value: Uint8Array | string | {}): void; 24 | /** 25 | * Displays a string 26 | */ 27 | export function text(value: Uint8Array): void; 28 | 29 | // tslab stuff. 30 | /** 31 | * Displays an HTML outut. 32 | */ 33 | export function html(s: string): void; 34 | /** 35 | * Load & executes javacsript in the output. 36 | */ 37 | export function javascript(s: string): void; 38 | /** 39 | * Displays a markdown string in the output. 40 | */ 41 | export function markdown(s: string): void; 42 | /** 43 | * Displays latext in the output. 44 | */ 45 | // export function latex(s: string): void; 46 | /** 47 | * Displays an SVG image. 48 | */ 49 | export function svg(s: string): void; 50 | /** 51 | * Displays a png image. 52 | */ 53 | export function png(b: Uint8Array): void; 54 | /** 55 | * Displays a jpeg image. 56 | */ 57 | export function jpeg(b: Uint8Array): void; 58 | /** 59 | * Displays a gif image. 60 | */ 61 | export function gif(b: Uint8Array): void; 62 | /** 63 | * Displays some text. 64 | */ 65 | export function text(s: string): void; 66 | } 67 | 68 | export namespace Plotly { 69 | /** 70 | * Renders a plotly plot. 71 | * See detailed documentation here https://plotly.com/javascript/plotlyjs-function-reference/#plotlynewplot 72 | */ 73 | export function newPlot( 74 | root: plotly.Root, 75 | data: plotly.Data[], 76 | layout?: Partial, 77 | config?: Partial 78 | ): Promise; 79 | /** 80 | * Returns a base64 encoded string representation of the generated plot. 81 | * @param {plotly.Data[]} data See detailed documentation here https://plotly.com/javascript/plotlyjs-function-reference/#plotlynewplot 82 | * @param {plotly.Layout} layout See detailed documentation here https://plotly.com/javascript/plotlyjs-function-reference/#plotlynewplot 83 | * @param {('png' | 'svg' | 'jpeg')} [format] Defaults to 'png' if not specified. 84 | */ 85 | export function toBase64( 86 | data: plotly.Data[], 87 | layout: plotly.Layout, 88 | format?: 'png' | 'svg' | 'jpeg' 89 | ): Promise; 90 | /** 91 | * Saves the generated plot into a file. 92 | * Return the path to the file name (if a file path is not provided a temporary image file is created and returned). 93 | * @param {plotly.Data[]} data See detailed documentation here https://plotly.com/javascript/plotlyjs-function-reference/#plotlynewplot 94 | * @param {plotly.Layout} layout See detailed documentation here https://plotly.com/javascript/plotlyjs-function-reference/#plotlynewplot 95 | * @param {('png' | 'svg' | 'jpeg')} [format] Defaults to 'png' if not specified. 96 | * @param {string} [file] Destination file path for the image to be downloaded. 97 | * If not specified, the image is downloaded into a temporary file and that path is returned. 98 | * @return {*} {Promise} 99 | */ 100 | export function toFile( 101 | data: plotly.Data[], 102 | layout: plotly.Layout, 103 | format?: 'png' | 'svg' | 'jpeg', 104 | file?: string 105 | ): Promise; 106 | } 107 | -------------------------------------------------------------------------------- /src/node-kernel/index.js: -------------------------------------------------------------------------------- 1 | function noop() { 2 | // 3 | } 4 | module.exports = { 5 | display: { 6 | javascript: noop, 7 | html: noop, 8 | markdown: noop, 9 | latex: noop, 10 | svg: noop, 11 | png: noop, 12 | jpeg: noop, 13 | gif: noop, 14 | pdf: noop, 15 | text: noop, 16 | // New items. 17 | image: noop, 18 | appendImage: noop, 19 | json: noop 20 | }, 21 | Plotly: { 22 | newPlot: noop, 23 | toBase64: noop, 24 | toFile: noop 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/node-kernel/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@donjayamanne/node-kernel", 3 | "version": "1.0.2", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /src/node-kernel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-kernel", 3 | "version": "1.0.2", 4 | "description": "Contains an npm package that provides helper functions for [Type Script notebooks in VS Code](https://marketplace.visualstudio.com/items?itemName=donjayamanne.typescript-notebook)", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": { 10 | "name": "Don Jayamanne" 11 | }, 12 | "homepage": "https://marketplace.visualstudio.com/items?itemName=donjayamanne.typescript-notebook", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/DonJayamanne/typescript-notebook" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/DonJayamanne/typescript-notebook" 19 | }, 20 | "types": "./index.d.ts", 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /src/node-kernel/plotly.js/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Don Jayamanne. 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 | -------------------------------------------------------------------------------- /src/node-kernel/plotly.js/lib/core.d.ts: -------------------------------------------------------------------------------- 1 | export * from '..'; 2 | -------------------------------------------------------------------------------- /src/node-kernel/plotly.js/lib/traces/box.d.ts: -------------------------------------------------------------------------------- 1 | import { PlotData, Color, MarkerSymbol } from '../../index'; 2 | import { ScatterSelectedMarker } from './scatter'; 3 | 4 | // See https://github.com/plotly/plotly.js/blob/master/src/traces/box/attributes.js 5 | export interface BoxPlotData extends PlotData { 6 | type: 'box'; 7 | x0: any; 8 | y0: any; 9 | width: number; 10 | quartilemethod: 'linear' | 'exclusive' | 'inclusive'; 11 | boxpoints: 'all' | 'outliers' | 'suspectedoutliers' | false; 12 | jitter: number; 13 | pointpos: number; 14 | marker: Partial; 15 | offsetgroup: string; 16 | alignmentgroup: string; 17 | selected: ScatterSelectedMarker; 18 | unselected: ScatterSelectedMarker; 19 | } 20 | 21 | export interface BoxPlotMarker { 22 | outliercolor: Color; 23 | symbol: MarkerSymbol; 24 | opacity: number; 25 | size: number; 26 | color: Color; 27 | line: Partial<{ 28 | color: Color; 29 | width: number; 30 | outliercolor: Color; 31 | outlierwidth: number; 32 | }>; 33 | } 34 | -------------------------------------------------------------------------------- /src/node-kernel/plotly.js/lib/traces/candlestick.d.ts: -------------------------------------------------------------------------------- 1 | import { OhclData } from './ohcl'; 2 | 3 | export interface CandlestickData { 4 | type: 'candlestick'; 5 | name: OhclData['name']; 6 | 7 | /** 8 | * @default true 9 | */ 10 | visible: OhclData['visible']; 11 | 12 | /** 13 | * @default true 14 | */ 15 | showlegend: OhclData['showlegend']; 16 | 17 | /** 18 | * number between 0 and 1 19 | * @default 1 20 | */ 21 | opacity: OhclData['opacity']; 22 | 23 | ids: OhclData['ids']; 24 | 25 | /** 26 | * @default 0 27 | */ 28 | xperiod: OhclData['xperiod']; 29 | 30 | xperiod0: OhclData['xperiod0']; 31 | 32 | /** 33 | * @default "middle" 34 | */ 35 | xperiodalignment: OhclData['xperiodalignment']; 36 | 37 | x: OhclData['x']; 38 | open: OhclData['open']; 39 | high: OhclData['high']; 40 | close: OhclData['close']; 41 | low: OhclData['low']; 42 | 43 | /** 44 | * @default "" 45 | */ 46 | text: OhclData['text']; 47 | 48 | /** 49 | * @default "" 50 | */ 51 | hovertext: OhclData['hovertext']; 52 | 53 | /** 54 | * @default "all" 55 | */ 56 | hoverinfo: OhclData['hoverinfo']; 57 | 58 | meta: OhclData['meta']; 59 | 60 | /** 61 | * @default "x" 62 | */ 63 | xaxis: OhclData['xaxis']; 64 | 65 | /** 66 | * @default width=2 67 | */ 68 | line: { width?: number | undefined }; 69 | 70 | increasing: { 71 | line?: { 72 | color?: string | undefined; 73 | width?: number | undefined; 74 | } | undefined; 75 | }; 76 | 77 | decreasing: { 78 | line?: { 79 | color?: string | undefined; 80 | width?: number | undefined; 81 | } | undefined; 82 | }; 83 | 84 | hoverlabel: OhclData['hoverlabel']; 85 | 86 | /** 87 | * Number between 0 and 1. 88 | * 89 | * Selects the width of the whiskers relative to the box´s width. 90 | * For example, with 1, the whiskers are as wide as the box(es). 91 | * @default 0 92 | */ 93 | whiskerwidth: number; 94 | } 95 | -------------------------------------------------------------------------------- /src/node-kernel/plotly.js/lib/traces/ohcl.d.ts: -------------------------------------------------------------------------------- 1 | // Types for plotly.js/src/traces/ohcl/attributes.js 2 | 3 | export type HoverInfo = 4 | | 'x' 5 | | 'y' 6 | | 'z' 7 | | 'text' 8 | | 'name' 9 | | 'x+y' 10 | | 'x+z' 11 | | 'x+text' 12 | | 'x+name' 13 | | 'y+z' 14 | | 'y+text' 15 | | 'y+name' 16 | | 'z+text' 17 | | 'z+name' 18 | | 'x+y+z' 19 | | 'x+y+text' 20 | | 'x+y+name' 21 | | 'y+z+text' 22 | | 'y+z+name' 23 | | 'z+text+name' 24 | | 'all' 25 | | 'none' 26 | | 'skip'; 27 | 28 | export type Dash = 'solid' | 'dot' | 'dash' | 'longdash' | 'dashdot' | 'longdashdot'; 29 | 30 | export type XCalendar = 31 | | 'gregorian' 32 | | 'chinese' 33 | | 'coptic' 34 | | 'discworld' 35 | | 'ethiopian' 36 | | 'hebrew' 37 | | 'islamic' 38 | | 'julian' 39 | | 'mayan' 40 | | 'nanakshahi' 41 | | 'nepali' 42 | | 'persian' 43 | | 'jalali' 44 | | 'taiwan' 45 | | 'thai' 46 | | 'ummalqura'; 47 | 48 | export interface OhclData { 49 | type: 'ohcl'; 50 | name: string; 51 | visible: boolean | 'legendonly'; 52 | showlegend: boolean; 53 | legendgroup: string; 54 | opacity: number; 55 | ids: string[]; 56 | x: string[]; 57 | close: number[]; 58 | open: number[]; 59 | high: number[]; 60 | low: number[]; 61 | text: string | string[]; 62 | hovertext: string | string[]; 63 | hoverinfo: HoverInfo; 64 | meta: any; // TODO: further refine 65 | customdata: any[]; // TODO: further refine 66 | xaxis: string; // TODO: should depend on the layout 67 | yaxis: string; // TODO: should depend on the layout 68 | xperiod: any; // TODO: further refine 69 | xperiodalignment: 'start' | 'middle' | 'end'; 70 | xperiod0: any; // TODO: further refine; 71 | line: { 72 | width: number; 73 | dash: Dash; 74 | }; 75 | selectedpoints: any; // TODO: further refine 76 | increasing: { 77 | line?: { 78 | color?: string | undefined; 79 | width?: number | undefined; 80 | dash?: Dash | undefined; 81 | } | undefined; 82 | }; 83 | decreasing: { 84 | line?: { 85 | color?: string | undefined; 86 | width?: number | undefined; 87 | dash?: Dash | undefined; 88 | } | undefined; 89 | }; 90 | 91 | hoverlabel: { 92 | bgcolor?: string | string[] | undefined; 93 | bordercolor?: string | string[] | undefined; 94 | font?: { 95 | family?: string | string[] | undefined; 96 | size?: number | undefined; 97 | color?: string | string[] | undefined; 98 | } | undefined; 99 | align?: 'left' | 'right' | 'auto' | undefined; 100 | namelength?: number | number[] | undefined; 101 | split?: boolean | undefined; 102 | }; 103 | tickwidth: number; 104 | xcalendar: XCalendar; 105 | uirevision: any; // TODO: further refine 106 | } 107 | -------------------------------------------------------------------------------- /src/node-kernel/plotly.js/lib/traces/pie.d.ts: -------------------------------------------------------------------------------- 1 | import { PlotData, DataTitle, Datum, HoverLabel } from '../../index'; 2 | 3 | export type PieColor = string | number; 4 | export type PieColors = Array; 5 | 6 | export interface PieFont { 7 | family: string | string[]; 8 | size: number | number[]; 9 | color: PieColor | PieColors; 10 | } 11 | 12 | export interface PieDataTitle extends Pick { 13 | font: Partial; 14 | } 15 | 16 | export type PieTextPosition = 'inside' | 'outside' | 'auto' | 'none'; 17 | 18 | export type PieHoverInfo = 19 | | 'all' 20 | | 'none' 21 | | 'skip' 22 | | 'label' 23 | | 'text' 24 | | 'value' 25 | | 'percent' 26 | | 'name' 27 | | 'label+text' 28 | | 'label+value' 29 | | 'label+percent' 30 | | 'label+name' 31 | | 'text+value' 32 | | 'text+percent' 33 | | 'text+name' 34 | | 'value+percent' 35 | | 'value+name' 36 | | 'percent+name' 37 | | 'label+text+value' 38 | | 'label+text+percent' 39 | | 'label+text+name' 40 | | 'label+value+percent' 41 | | 'label+value+name' 42 | | 'label+percent+name' 43 | | 'text+value+percent' 44 | | 'text+value+name' 45 | | 'text+percent+name' 46 | | 'value+percent+name' 47 | | 'label+text+value+percent' 48 | | 'label+text+value+name' 49 | | 'label+text+percent+name' 50 | | 'label+value+percent+name' 51 | | 'text+value+percent+name'; 52 | 53 | export interface PieDomain { 54 | x: number[]; 55 | y: number[]; 56 | row: number; 57 | column: number; 58 | } 59 | 60 | export interface PieLine { 61 | color: PieColor | PieColors; 62 | width: number | number[]; 63 | } 64 | 65 | export interface PieMarker { 66 | colors: PieColors; 67 | line: Partial; 68 | } 69 | 70 | export interface PieHoverLabel { 71 | bgcolor: PieColor | PieColors; 72 | bordercolor: PieColor | PieColors; 73 | font: PieFont; 74 | align: HoverLabel['align'] | Array; 75 | namelength: number | number[]; 76 | } 77 | 78 | export type PieInsideTextOrientation = 'horizontal' | 'radial' | 'tangential' | 'auto'; 79 | 80 | export interface PieData extends Pick { 95 | type: 'pie'; 96 | title: Partial; 97 | values: Array; 98 | dlabel: number; 99 | label0: number; 100 | pull: number | number[]; 101 | text: Datum | Datum[]; 102 | textposition: PieTextPosition | PieTextPosition[]; 103 | texttemplate: string | string[]; 104 | hoverinfo: PieHoverInfo; 105 | hovertemplate: string | string[]; 106 | meta: number | string; 107 | customdata: Datum[]; 108 | domain: Partial; 109 | marker: Partial; 110 | textfont: PieFont; 111 | hoverlabel: Partial; 112 | insidetextfont: PieFont; 113 | insidetextorientation: PieInsideTextOrientation; 114 | outsidetextfont: PieFont; 115 | scalegroup: string; 116 | sort: boolean; 117 | uirevision: number | string; 118 | } 119 | -------------------------------------------------------------------------------- /src/node-kernel/plotly.js/lib/traces/scatter.d.ts: -------------------------------------------------------------------------------- 1 | import { Color } from '../../index'; 2 | 3 | export interface ScatterSelectedMarker { 4 | marker: Partial<{ 5 | opacity: number; 6 | color: Color; 7 | }>; 8 | textfont: { color: Color }; 9 | } 10 | -------------------------------------------------------------------------------- /src/node-kernel/plotly.js/lib/traces/violin.d.ts: -------------------------------------------------------------------------------- 1 | import { BoxPlotData } from './box'; 2 | import { Color } from '../..'; 3 | 4 | // See https://github.com/plotly/plotly.js/blob/master/src/traces/violin/attributes.js 5 | export interface ViolinData { 6 | type: 'violin'; 7 | 8 | x: BoxPlotData['x']; 9 | y: BoxPlotData['y']; 10 | x0: BoxPlotData['x0']; 11 | y0: BoxPlotData['y0']; 12 | name: BoxPlotData['name']; 13 | 14 | opacity: number; // Missing from the list of attributes 15 | 16 | orientation: BoxPlotData['orientation']; 17 | bandwidth: number; 18 | scalegroup: string; 19 | scalemode: 'width' | 'count'; 20 | spanmode: 'soft' | 'hard' | 'manual'; 21 | span: any[]; 22 | line: Partial<{ 23 | color: Color; 24 | width: number; 25 | }>; 26 | fillcolor: Color; 27 | points: BoxPlotData['boxpoints']; 28 | jitter: BoxPlotData['jitter']; 29 | pointpos: BoxPlotData['pointpos']; 30 | width: BoxPlotData['width']; 31 | marker: BoxPlotData['marker']; 32 | text: BoxPlotData['text']; 33 | hovertext: BoxPlotData['hovertext']; 34 | hovertemplate: BoxPlotData['hovertemplate']; 35 | box: Partial<{ 36 | visible: boolean; 37 | width: number; 38 | fillcolor: Color; 39 | line: Partial<{ 40 | color: Color; 41 | width: number; 42 | }>; 43 | }>; 44 | meanline: Partial<{ 45 | visible: boolean; 46 | color: Color; 47 | width: number; 48 | }>; 49 | side: 'both' | 'positive' | 'negative'; 50 | 51 | offsetgroup: BoxPlotData['offsetgroup']; 52 | alignmentgroup: BoxPlotData['alignmentgroup']; 53 | 54 | selected: BoxPlotData['selected']; 55 | unselected: BoxPlotData['unselected']; 56 | 57 | hoveron: 'violins' | 'points' | 'kde' | 'all' | string; 58 | } 59 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath, launchArgs: ['--no-sandbox'] }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/TESTS.md: -------------------------------------------------------------------------------- 1 | * Update stack traces in error outputs 2 | * Ensure we fix the source maps (lines, columns should be updated) 3 | * Assume we have a function defined in cell 2 that invokes a function defined in cell 1 4 | If there are errors in cell 1 function, then when we run cell 2, the stack trace should include lines that point to cell 1 & cell 2 5 | When testing, ensure we have multiple lines from the same cell in the stack trace. 6 | * Ensure we kill the shell process 7 | * Source maps are all wrong 8 | Easy way to confirm is with debugging (& inline breakpoints) 9 | * Ensure outputs 10 | ```javascript 11 | console.log(1); 12 | ``` 13 | ```javascript 14 | setTimeout(() => console.log(1), 1000); 15 | ``` 16 | ```javascript 17 | await new Promise(resolve, setTimeout(resolve, 1000)); 18 | console.log(1); 19 | ``` 20 | ```javascript 21 | for (let i = 0; i < 10; i++){ 22 | await new Promise(resolve, setTimeout(resolve, 1000)); 23 | console.log(1); 24 | } 25 | ``` 26 | ```javascript 27 | // Sometimes we loose output (VSC bug) 28 | for (let i = 0; i < 10; i++){ 29 | await new Promise(resolve, setTimeout(resolve, 100)); 30 | console.log(1); 31 | } 32 | ``` 33 | ```javascript 34 | // Sometimes the order is incorrect (might not yet be fixed) 35 | console.log(1324); 36 | 9999 37 | ``` 38 | ```javascript 39 | // Sometimes the order is incorrect (might not yet be fixed) 40 | import {display} from 'node-kernel'; 41 | display.text('1234'); 42 | 9999 43 | ``` 44 | ```javascript 45 | // Sometimes the order is incorrect (might not yet be fixed) 46 | import {display} from 'node-kernel'; 47 | display.text('1234'); 48 | 9999 49 | ``` 50 | ```javascript 51 | var a = 1243; 52 | a 53 | ``` 54 | ```javascript 55 | a = 1243; 56 | ``` 57 | ```javascript 58 | // Test string literals 59 | `Value of a is ${a}`; 60 | ``` 61 | ```javascript 62 | // Circular references in JSON output. 63 | setTimeout(() => console.log(1324), 1000); 64 | ``` 65 | ```typescript 66 | import * as fs from 'fs/promises'; 67 | ``` 68 | ```typescript 69 | // Ensure imports in previous cells are available. 70 | const image = await fs.readFile('.....'); 71 | image 72 | ``` 73 | ```typescript 74 | // Ensure imports in previous cells are available. 75 | const image = await fs.readFile('.....'); 76 | image 77 | ``` 78 | ```typescript 79 | // Test default imports, named imports (in subsequent cells) 80 | import xyz from 'abc'; 81 | import {wow} from 'another'; 82 | import {wow, that:isAwesome} from 'another'; 83 | import type {wow, that:isAwesome} from 'another'; 84 | ``` 85 | ```typescript 86 | // Run this and ensure the source maps are right, it will be wrong today, needs to be fixed. 87 | console.log(x) 88 | ``` 89 | 90 | * Test source maps for the following 91 | ```javascript 92 | // Add unnecessary leading white space. 93 | var a = 1234; 94 | ``` 95 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd' 9 | }); 10 | // (mocha as any).useColors(true); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | process.env.__IS_TEST = 'true'; 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run((failures) => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /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 | "noImplicitAny": false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.client.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./src/tsconfig-base.json", 3 | "compilerOptions": { 4 | // noEmit prevents the default tsc from building this--we use webpack instead 5 | "noEmit": true, 6 | "rootDir": "src", 7 | "module": "esnext", 8 | "lib": [ 9 | "ES2019", 10 | "dom" 11 | ], 12 | "jsx": "react" 13 | }, 14 | "exclude": [ 15 | "temp" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./src/tsconfig-base.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "module": "commonjs", 6 | "target": "ES2019", 7 | "lib": [ 8 | "ES2019", 9 | "DOM" 10 | ], 11 | "skipLibCheck": true, 12 | "outDir": "out" 13 | }, 14 | "exclude": [ 15 | "src/client", 16 | "temp" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------