├── .eslintrc.json
├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
└── settings.json
├── .vscodeignore
├── LICENSE
├── README.md
├── icon.png
├── package-lock.json
├── package.json
├── src
├── Controller.ts
├── IBMiSerializer.ts
├── base.ts
└── extension.ts
├── tsconfig.json
└── webpack.config.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": {
5 | "ecmaVersion": 6,
6 | "sourceType": "module"
7 | },
8 | "plugins": [
9 | "@typescript-eslint"
10 | ],
11 | "rules": {
12 | "@typescript-eslint/naming-convention": "warn",
13 | "@typescript-eslint/semi": "warn",
14 | "curly": "warn",
15 | "eqeqeq": "warn",
16 | "no-throw-literal": "warn",
17 | "semi": "off",
18 | "no-const-assign": "warn",
19 | "no-this-before-super": "warn",
20 | "no-unreachable": "warn",
21 | "no-var": "error",
22 | "quotes": ["warn", "backtick"],
23 | "constructor-super": "warn",
24 | "valid-typeof": "warn",
25 | "indent": ["error", 2]
26 | },
27 | "ignorePatterns": [
28 | "out",
29 | "dist",
30 | "**/*.d.ts"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | release:
3 | types: [created]
4 |
5 | jobs:
6 | release:
7 | name: Release and publish
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/setup-node@v2
11 | with:
12 | node-version: '16'
13 | - uses: actions/checkout@v2
14 |
15 | - run: npm install
16 |
17 | - run: npm install -g vsce ovsx
18 |
19 | - name: Publish to Marketplace
20 | run: vsce publish -p $PUBLISHER_TOKEN
21 | env:
22 | PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }}
23 |
24 | - name: Publish to Open VSX
25 | run: npx ovsx publish -p ${{ secrets.OPENVSX_TOKEN }}
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | dist
3 | node_modules
4 | .vscode-test/
5 | *.vsix
6 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": [
5 | "dbaeumer.vscode-eslint",
6 | "eamodio.tsl-problem-matcher"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}"
14 | ],
15 | "outFiles": [
16 | "${workspaceFolder}/dist/**/*.js"
17 | ],
18 | "preLaunchTask": "npm: compile",
19 | "sourceMaps": true
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/.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 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files
6 | },
7 | "search.exclude": {
8 | "out": true, // set this to false to include "out" folder in search results
9 | "dist": true // set this to false to include "dist" folder in search results
10 | },
11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
12 | "typescript.tsc.autoDetect": "off"
13 | }
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/**
4 | node_modules/**
5 | src/**
6 | .gitignore
7 | .yarnrc
8 | vsc-extension-quickstart.md
9 | **/tsconfig.json
10 | **/.eslintrc.json
11 | **/*.map
12 | **/*.ts
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Halcyon Tech Ltd
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 | # IBM i Notebooks
2 |
3 |
4 |
5 | Adds an IBM i Notebook, which allows users to create notebooks to:
6 |
7 | * run CL commands
8 | * run commands in PASE (`shellscript`)
9 | * execute SQL statements
10 |
11 | **Depends on Code for IBM i.**
12 |
13 | ## What is the point of a Notebook?
14 |
15 | > Notebooks are documents that contain a mix of rich Markdown, executable code snippets, and accompanying rich output. These are all separated into distinct cells and can be interleaved in any order.
16 | >
17 | > Notebooks are great storytelling devices, allowing you to interleave Markdown elements like images, math equations, and explanatory text with your code. Notebooks can be a perfect way to share and explain your ideas with coworkers or the public community.
18 |
19 | *[Read more on the Visual Studio Code website.](https://code.visualstudio.com/blogs/2021/11/08/custom-notebooks)*
20 |
21 | ## How to use
22 |
23 | 1. Connect to an IBM i using Code for IBM i.
24 | 2. Create a file with the `.inb` extension
25 | 3. Open an `.inb` file
26 | * You can open `.inb` files in the IFS (not source members), **or**
27 | * you can open local `.inb` files.
28 | * You can also use the 'New Notebook' command to create a new local Notebook. Use F1/Ctrl+Shift+P and search 'new notebook'
29 |
30 | ## CL commands
31 |
32 | When CL commands are executed, they will use the library list that is setup in Code for IBM i. If you have CL content assist enabled (as part of Code for IBM i), then you will be able to use utilise that when writing CL commands.
33 |
34 | ## SQL statements
35 |
36 | SQL statements run in SQL mode (`*SQL`) and will always have a default library that matches the user's user profile name. Any unqualified objects will use the default library.
37 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefori/vscode-ibmi-notebooks/77cccc5d0b2a975795abb882ca68e911aa79472b/icon.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vscode-ibmi-notebooks",
3 | "icon": "icon.png",
4 | "displayName": "IBM i Notebooks",
5 | "description": "VS Code Notebooks for IBM i",
6 | "version": "0.0.6",
7 | "engines": {
8 | "vscode": "^1.65.0"
9 | },
10 | "author": {
11 | "name": "Halcyon Tech Ltd",
12 | "url": "https://github.com/halcyon-tech"
13 | },
14 | "publisher": "halcyontechltd",
15 | "repository": {
16 | "url": "https://github.com/halcyon-tech/vscode-ibmi-notebooks"
17 | },
18 | "license": "MIT",
19 | "extensionDependencies": [
20 | "halcyontechltd.code-for-ibmi"
21 | ],
22 | "categories": [
23 | "Notebooks"
24 | ],
25 | "activationEvents": [
26 | "onNotebook:ibmi-notebook",
27 | "onStartupFinished"
28 | ],
29 | "main": "./dist/extension.js",
30 | "contributes": {
31 | "commands": [
32 | {
33 | "command": "vscode-ibmi-notebooks.open",
34 | "title": "New Notebook",
35 | "category": "IBM i Notebooks"
36 | }
37 | ],
38 | "notebooks": [
39 | {
40 | "id": "ibmi-notebook",
41 | "type": "ibmi-notebook",
42 | "displayName": "IBM i Notebook",
43 | "selector": [
44 | {
45 | "filenamePattern": "*.inb"
46 | }
47 | ]
48 | }
49 | ]
50 | },
51 | "scripts": {
52 | "vscode:prepublish": "npm run package",
53 | "compile": "webpack",
54 | "watch": "webpack --watch",
55 | "package": "webpack --mode production --devtool hidden-source-map",
56 | "test-compile": "tsc -p ./",
57 | "test-watch": "tsc -watch -p ./",
58 | "pretest": "npm run test-compile && npm run lint",
59 | "lint": "eslint src --ext ts"
60 | },
61 | "devDependencies": {
62 | "@halcyontech/vscode-ibmi-types": "^2.0.0",
63 | "@types/node": "14.x",
64 | "@types/vscode": "^1.65.0",
65 | "@typescript-eslint/eslint-plugin": "^4.26.0",
66 | "@typescript-eslint/parser": "^4.26.0",
67 | "eslint": "^7.27.0",
68 | "ts-loader": "^9.2.2",
69 | "typescript": "^4.3.2",
70 | "webpack": "^5.38.1",
71 | "webpack-cli": "^4.7.0"
72 | },
73 | "dependencies": {
74 | "json-to-markdown-table": "^1.0.0"
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Controller.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | //@ts-ignore
4 | import * as mdTable from 'json-to-markdown-table';
5 | import { getInstance } from './base';
6 | import { CommandResult } from '@halcyontech/vscode-ibmi-types';
7 |
8 | export default class IBMiController {
9 | readonly controllerId = `ibmi-notebook-controller-id`;
10 | readonly notebookType = `ibmi-notebook`;
11 | readonly label = `IBM i Notebook`;
12 | readonly supportedLanguages = [`sql`, `cl`, `shellscript`];
13 |
14 | private readonly _controller: vscode.NotebookController;
15 | private _executionOrder = 0;
16 |
17 | constructor() {
18 | this._controller = vscode.notebooks.createNotebookController(
19 | this.controllerId,
20 | this.notebookType,
21 | this.label
22 | );
23 |
24 | this._controller.supportedLanguages = this.supportedLanguages;
25 | this._controller.supportsExecutionOrder = true;
26 | this._controller.executeHandler = this._execute.bind(this);
27 | }
28 |
29 | public dispose() {
30 | this._controller.dispose();
31 | }
32 |
33 | private _execute(
34 | cells: vscode.NotebookCell[],
35 | _notebook: vscode.NotebookDocument,
36 | _controller: vscode.NotebookController
37 | ): void {
38 | for (let cell of cells) {
39 | this._doExecution(cell);
40 | }
41 | }
42 |
43 | private async _doExecution(cell: vscode.NotebookCell): Promise {
44 | const instance = getInstance();
45 | const connection = instance?.getConnection();
46 | const content = instance?.getContent();
47 | const items: vscode.NotebookCellOutputItem[] = [];
48 |
49 | const execution = this._controller.createNotebookCellExecution(cell);
50 | execution.executionOrder = ++this._executionOrder;
51 | execution.start(Date.now()); // Keep track of elapsed time to execute cell.
52 |
53 | if (connection && content) {
54 | switch (cell.document.languageId) {
55 | case `sql`:
56 | try {
57 | const table: Object[] = await content.runSQL(cell.document.getText());
58 | const keys = Object.keys(table[0]);
59 |
60 | // Add `-` for blanks.
61 | table.forEach(row => {
62 | keys.forEach(key => {
63 | //@ts-ignore
64 | if (row[key] === ``) { row[key] = `-`; }
65 | });
66 | });
67 |
68 | items.push(vscode.NotebookCellOutputItem.text(mdTable(table, keys), `text/markdown`));
69 | } catch (e) {
70 | //@ts-ignore
71 | items.push(vscode.NotebookCellOutputItem.stderr(e.message));
72 | }
73 | break;
74 |
75 | case `cl`:
76 | try {
77 | const command = await connection.runCommand({
78 | command: cell.document.getText(),
79 | environment: `ile`
80 | });
81 |
82 | if (command.stdout) {
83 | items.push(vscode.NotebookCellOutputItem.text([
84 | `\`\`\``,
85 | command.stdout,
86 | `\`\`\``
87 | ].join(`\n`), `text/markdown`));
88 | }
89 |
90 | if (command.stderr) {
91 | items.push(vscode.NotebookCellOutputItem.text([
92 | `\`\`\``,
93 | command.stderr,
94 | `\`\`\``
95 | ].join(`\n`), `text/markdown`));
96 | }
97 | } catch (e) {
98 | items.push(
99 | vscode.NotebookCellOutputItem.stderr(`Failed to runCommand. Are you connected?`),
100 | //@ts-ignore
101 | vscode.NotebookCellOutputItem.stderr(e.message)
102 | );
103 | }
104 | break;
105 |
106 | case `shellscript`:
107 | try {
108 | const command: CommandResult = await vscode.commands.executeCommand(`code-for-ibmi.runCommand`, {
109 | command: cell.document.getText(),
110 | environment: `pase`
111 | });
112 |
113 | if (command.stdout) {
114 | items.push(vscode.NotebookCellOutputItem.text([
115 | `\`\`\``,
116 | command.stdout,
117 | `\`\`\``
118 | ].join(`\n`), `text/markdown`));
119 | }
120 |
121 | if (command.stderr) {
122 | items.push(vscode.NotebookCellOutputItem.text([
123 | `\`\`\``,
124 | command.stderr,
125 | `\`\`\``
126 | ].join(`\n`), `text/markdown`));
127 | }
128 | } catch (e) {
129 | items.push(
130 | vscode.NotebookCellOutputItem.stderr(`Failed to runCommand. Are you connected?`),
131 | //@ts-ignore
132 | vscode.NotebookCellOutputItem.stderr(e.message)
133 | );
134 | }
135 | break;
136 | }
137 |
138 | } else {
139 | items.push(
140 | vscode.NotebookCellOutputItem.stderr(`Failed to execute. Are you connected?`)
141 | )
142 | }
143 |
144 | execution.replaceOutput([
145 | new vscode.NotebookCellOutput(items)
146 | ]);
147 |
148 | execution.end(true, Date.now());
149 | }
150 | }
--------------------------------------------------------------------------------
/src/IBMiSerializer.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { TextDecoder, TextEncoder } from 'util';
3 |
4 | interface RawNotebookCell {
5 | language: string;
6 | value: string;
7 | kind: vscode.NotebookCellKind;
8 | }
9 |
10 | export default class IBMiSerializer implements vscode.NotebookSerializer {
11 | async deserializeNotebook(
12 | content: Uint8Array,
13 | _token: vscode.CancellationToken
14 | ): Promise {
15 | let contents = new TextDecoder().decode(content);
16 |
17 | let raw: RawNotebookCell[];
18 | try {
19 | raw = JSON.parse(contents);
20 | } catch {
21 | raw = [];
22 | }
23 |
24 | const cells = raw.map(
25 | item => new vscode.NotebookCellData(item.kind, item.value, item.language)
26 | );
27 |
28 | return new vscode.NotebookData(cells);
29 | }
30 |
31 | async serializeNotebook(
32 | data: vscode.NotebookData,
33 | _token: vscode.CancellationToken
34 | ): Promise {
35 | let contents: RawNotebookCell[] = [];
36 |
37 | for (const cell of data.cells) {
38 | contents.push({
39 | kind: cell.kind,
40 | language: cell.languageId,
41 | value: cell.value
42 | });
43 | }
44 |
45 | return new TextEncoder().encode(JSON.stringify(contents));
46 | }
47 | }
--------------------------------------------------------------------------------
/src/base.ts:
--------------------------------------------------------------------------------
1 | import { CodeForIBMi } from "@halcyontech/vscode-ibmi-types";
2 | import Instance from "@halcyontech/vscode-ibmi-types/api/Instance";
3 | import { Extension, extensions } from "vscode";
4 |
5 | let baseExtension: Extension|undefined;
6 |
7 | export function loadBase(): CodeForIBMi|undefined {
8 | if (!baseExtension) {
9 | baseExtension = (extensions ? extensions.getExtension(`halcyontechltd.code-for-ibmi`) : undefined);
10 | }
11 |
12 | return (baseExtension && baseExtension.isActive && baseExtension.exports ? baseExtension.exports : undefined);
13 | }
14 |
15 | export function getInstance(): Instance|undefined {
16 | return (baseExtension && baseExtension.isActive && baseExtension.exports ? baseExtension.exports.instance : undefined);
17 | }
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | // The module 'vscode' contains the VS Code extensibility API
2 | // Import the module and reference it with the alias vscode in your code below
3 | import * as vscode from 'vscode';
4 |
5 | import IBMiSerializer from './IBMiSerializer';
6 | import IBMiController from './Controller';
7 | import { loadBase } from './base';
8 |
9 | // this method is called when your extension is activated
10 | // your extension is activated the very first time the command is executed
11 | export function activate(context: vscode.ExtensionContext) {
12 |
13 | // Use the console to output diagnostic information (console.log) and errors (console.error)
14 | // This line of code will only be executed once when your extension is activated
15 | console.log(`Congratulations, your extension "vscode-ibmi-notebooks" is now active!`);
16 |
17 | const openNotebook = vscode.commands.registerCommand(`vscode-ibmi-notebooks.open`, (node) => {
18 | const uri = node ? node.resourceUri : vscode.Uri.parse(`untitled:` + `notebook.inb`);
19 |
20 | vscode.commands.executeCommand(`vscode.openWith`, uri, `ibmi-notebook`);
21 | });
22 |
23 | context.subscriptions.push(
24 | vscode.workspace.registerNotebookSerializer(`ibmi-notebook`, new IBMiSerializer()),
25 | new IBMiController(),
26 | openNotebook
27 | );
28 |
29 | loadBase();
30 | }
31 |
32 | // this method is called when your extension is deactivated
33 | export function deactivate() {}
34 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "out",
6 | "lib": [
7 | "es6"
8 | ],
9 | "sourceMap": true,
10 | "rootDir": "src",
11 | "strict": true /* enable all strict type-checking options */
12 | /* Additional Checks */
13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
16 | },
17 | "exclude": [
18 | "node_modules",
19 | ".vscode-test"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 |
3 | 'use strict';
4 |
5 | const path = require('path');
6 |
7 | /**@type {import('webpack').Configuration}*/
8 | const config = {
9 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
10 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
11 |
12 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
13 | output: {
14 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
15 | path: path.resolve(__dirname, 'dist'),
16 | filename: 'extension.js',
17 | libraryTarget: 'commonjs2'
18 | },
19 | devtool: 'nosources-source-map',
20 | externals: {
21 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
22 | // modules added here also need to be added in the .vsceignore file
23 | },
24 | resolve: {
25 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
26 | extensions: ['.ts', '.js']
27 | },
28 | module: {
29 | rules: [
30 | {
31 | test: /\.ts$/,
32 | exclude: /node_modules/,
33 | use: [
34 | {
35 | loader: 'ts-loader'
36 | }
37 | ]
38 | }
39 | ]
40 | }
41 | };
42 | module.exports = config;
--------------------------------------------------------------------------------