diff --git a/apps/vscode-wing/.eslintrc.json b/apps/vscode-wing/.eslintrc.json index fb2b2de6745..e0db8788bc1 100644 --- a/apps/vscode-wing/.eslintrc.json +++ b/apps/vscode-wing/.eslintrc.json @@ -58,6 +58,7 @@ "devDependencies": [ "**/test/**", "**/build-tools/**", + "src/**", ".projenrc.ts", "projenrc/**/*.ts" ], diff --git a/apps/vscode-wing/.projen/deps.json b/apps/vscode-wing/.projen/deps.json index 29a11112591..0846072ef41 100644 --- a/apps/vscode-wing/.projen/deps.json +++ b/apps/vscode-wing/.projen/deps.json @@ -1,14 +1,31 @@ { "dependencies": [ + { + "name": "@trpc/client", + "type": "build" + }, + { + "name": "@types/node-fetch", + "type": "build" + }, { "name": "@types/node", "version": "^16", "type": "build" }, + { + "name": "@types/vscode", + "version": "^1.70.0", + "type": "build" + }, { "name": "@types/which", "type": "build" }, + { + "name": "@types/ws", + "type": "build" + }, { "name": "@typescript-eslint/eslint-plugin", "version": "^5", @@ -24,7 +41,13 @@ "type": "build" }, { - "name": "esbuild", + "name": "@wingconsole/app", + "version": "workspace:^", + "type": "build" + }, + { + "name": "@wingconsole/server", + "version": "workspace:^", "type": "build" }, { @@ -52,11 +75,20 @@ "version": "^8", "type": "build" }, + { + "name": "node-fetch", + "version": "2", + "type": "build" + }, { "name": "npm-check-updates", "version": "^16", "type": "build" }, + { + "name": "open", + "type": "build" + }, { "name": "prettier", "type": "build" @@ -70,21 +102,24 @@ "type": "build" }, { - "name": "typescript", + "name": "tsup", "type": "build" }, { - "name": "@types/vscode", - "version": "^1.70.0", - "type": "runtime" + "name": "typescript", + "type": "build" }, { "name": "vscode-languageclient", - "type": "runtime" + "type": "build" }, { "name": "which", - "type": "runtime" + "type": "build" + }, + { + "name": "ws", + "type": "build" } ], "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." diff --git a/apps/vscode-wing/.projen/tasks.json b/apps/vscode-wing/.projen/tasks.json index 5f9c733780a..2f7b8062098 100644 --- a/apps/vscode-wing/.projen/tasks.json +++ b/apps/vscode-wing/.projen/tasks.json @@ -61,7 +61,7 @@ "description": "Only compile", "steps": [ { - "exec": "esbuild src/extension.ts --outfile=lib/index.js --external:node-gyp --external:vscode --format=cjs --platform=node --bundle" + "exec": "tsup" } ] }, @@ -165,19 +165,19 @@ "exec": "pnpm update npm-check-updates" }, { - "exec": "npm-check-updates --dep dev --upgrade --target=minor" + "exec": "npm-check-updates --dep dev --upgrade --target=minor --reject='@wingconsole/app,@wingconsole/server,node-fetch'" }, { - "exec": "npm-check-updates --dep optional --upgrade --target=minor" + "exec": "npm-check-updates --dep optional --upgrade --target=minor --reject='@wingconsole/app,@wingconsole/server,node-fetch'" }, { - "exec": "npm-check-updates --dep peer --upgrade --target=minor" + "exec": "npm-check-updates --dep peer --upgrade --target=minor --reject='@wingconsole/app,@wingconsole/server,node-fetch'" }, { - "exec": "npm-check-updates --dep prod --upgrade --target=minor" + "exec": "npm-check-updates --dep prod --upgrade --target=minor --reject='@wingconsole/app,@wingconsole/server,node-fetch'" }, { - "exec": "npm-check-updates --dep bundle --upgrade --target=minor" + "exec": "npm-check-updates --dep bundle --upgrade --target=minor --reject='@wingconsole/app,@wingconsole/server,node-fetch'" }, { "exec": "pnpm i --no-frozen-lockfile" @@ -198,7 +198,7 @@ "description": "Watch & compile in the background", "steps": [ { - "exec": "esbuild src/extension.ts --outfile=lib/index.js --external:node-gyp --external:vscode --format=cjs --platform=node --bundle --watch" + "exec": "tsup --watch" } ] } diff --git a/apps/vscode-wing/.projenrc.ts b/apps/vscode-wing/.projenrc.ts index 5d29d430914..9c65b4b9812 100644 --- a/apps/vscode-wing/.projenrc.ts +++ b/apps/vscode-wing/.projenrc.ts @@ -36,7 +36,7 @@ const project = new TypeScriptAppProject({ jest: false, github: false, npmignoreEnabled: false, - entrypoint: "lib/index.js", + entrypoint: "lib/extension.js", eslintOptions: { dirs: ["src"], prettier: true, @@ -48,17 +48,32 @@ const project = new TypeScriptAppProject({ tsconfig: { compilerOptions: { noUncheckedIndexedAccess: true, + lib: ["es2021"], }, }, - deps: [ + deps: [], + devDeps: [ `@types/vscode@^${VSCODE_BASE_VERSION}`, "vscode-languageclient", "which", + "@trpc/client", + "ws", + "open", + "node-fetch@2", + "@types/node", + "@types/which", + "@vscode/vsce", + "@types/node-fetch", + "@types/ws", + "@wingconsole/app@workspace:^", + "@wingconsole/server@workspace:^", ], - devDeps: ["@types/node", "@types/which", "esbuild", "@vscode/vsce"], }); +// because we're bundling, allow dev deps in src +project.eslint?.allowDevDeps("src/**"); + project.addGitIgnore("*.vsix"); const vscodeIgnore = new IgnoreFile(project, ".vscodeignore"); @@ -122,6 +137,26 @@ const contributes: VSCodeExtensionContributions = { dark: "resources/icon-dark.png", }, }, + { + command: "wingConsole.openResource", + title: "Open resource", + }, + { + command: "wingConsole.runTest", + title: "Run test", + icon: { + light: "resources/play-light.svg", + dark: "resources/play-dark.svg", + }, + }, + { + command: "wingConsole.runAllTests", + title: "Run all tests", + icon: { + light: "resources/play-all-light.svg", + dark: "resources/play-all-dark.svg", + }, + }, ], menus: { "editor/title": [ @@ -136,6 +171,20 @@ const contributes: VSCodeExtensionContributions = { group: "navigation", }, ], + "view/item/context": [ + { + command: "wingConsole.runTest", + when: "view == consoleTestsExplorer", + group: "inline", + }, + ], + "explorer/context": [ + { + command: "wingConsole.runAllTests", + when: "view == consoleTestsExplorer", + group: "inline", + }, + ], }, configuration: [ { @@ -150,6 +199,18 @@ const contributes: VSCodeExtensionContributions = { }, }, ], + views: { + explorer: [ + { + id: "consoleExplorer", + name: "Wing Resources", + }, + { + id: "consoleTestsExplorer", + name: "Wing Tests", + }, + ], + }, }; project.addFields({ @@ -166,11 +227,11 @@ project.addFields({ contributes, }); -const esbuildComment = - "esbuild src/extension.ts --outfile=lib/index.js --external:node-gyp --external:vscode --format=cjs --platform=node --bundle"; +project.addDevDeps("tsup"); + project.compileTask.reset(); -project.compileTask.exec(esbuildComment); -project.watchTask.reset(`${esbuildComment} --watch`); +project.compileTask.exec("tsup"); +project.watchTask.reset("tsup --watch"); project.packageTask.reset( "pnpm version ${PROJEN_BUMP_VERSION:-0.0.0} --allow-same-version" diff --git a/apps/vscode-wing/package.json b/apps/vscode-wing/package.json index 04353cc7ac0..b198f9531bd 100644 --- a/apps/vscode-wing/package.json +++ b/apps/vscode-wing/package.json @@ -29,28 +29,34 @@ "organization": true }, "devDependencies": { + "@trpc/client": "^10.30.0", "@types/node": "^16", + "@types/node-fetch": "^2.6.4", + "@types/vscode": "^1.70.0", "@types/which": "^2.0.2", + "@types/ws": "^8.5.5", "@typescript-eslint/eslint-plugin": "^5", "@typescript-eslint/parser": "^5", "@vscode/vsce": "^2.16.0", - "esbuild": "^0.17.4", + "@wingconsole/app": "workspace:^", + "@wingconsole/server": "workspace:^", "eslint": "^8", "eslint-config-prettier": "^8.6.0", "eslint-import-resolver-node": "^0.3.7", "eslint-import-resolver-typescript": "^3.5.3", "eslint-plugin-import": "^2.27.5", "eslint-plugin-prettier": "^4.2.1", + "node-fetch": "2", "npm-check-updates": "^16", + "open": "^8.4.0", "prettier": "^2.8.3", "projen": "^0.71.60", "ts-node": "^10.9.1", - "typescript": "^4.9.4" - }, - "dependencies": { - "@types/vscode": "^1.70.0", + "tsup": "^6.7.0", + "typescript": "^4.9.4", "vscode-languageclient": "^8.0.2", - "which": "^3.0.0" + "which": "^1.3.1", + "ws": "^8.13.0" }, "keywords": [ "cdk", @@ -65,14 +71,14 @@ "engines": { "vscode": "^1.70.0" }, - "main": "lib/index.js", + "main": "lib/extension.js", "license": "MIT", "homepage": "https://winglang.io", "version": "0.0.0", "bugs": { "url": "https://github.com/winglang/wing/issues" }, - "types": "lib/index.d.ts", + "types": "lib/extension.d.ts", "publisher": "Monada", "preview": true, "private": true, @@ -136,6 +142,26 @@ "light": "resources/icon-light.png", "dark": "resources/icon-dark.png" } + }, + { + "command": "wingConsole.openResource", + "title": "Open resource" + }, + { + "command": "wingConsole.runTest", + "title": "Run test", + "icon": { + "light": "resources/play-light.svg", + "dark": "resources/play-dark.svg" + } + }, + { + "command": "wingConsole.runAllTests", + "title": "Run all tests", + "icon": { + "light": "resources/play-all-light.svg", + "dark": "resources/play-all-dark.svg" + } } ], "menus": { @@ -150,6 +176,20 @@ "command": "wing.openFile", "group": "navigation" } + ], + "view/item/context": [ + { + "command": "wingConsole.runTest", + "when": "view == consoleTestsExplorer", + "group": "inline" + } + ], + "explorer/context": [ + { + "command": "wingConsole.runAllTests", + "when": "view == consoleTestsExplorer", + "group": "inline" + } ] }, "configuration": [ @@ -163,7 +203,19 @@ } } } - ] + ], + "views": { + "explorer": [ + { + "id": "consoleExplorer", + "name": "Wing Resources" + }, + { + "id": "consoleTestsExplorer", + "name": "Wing Tests" + } + ] + } }, "volta": { "extends": "../../package.json" diff --git a/apps/vscode-wing/resources/play-all-dark.svg b/apps/vscode-wing/resources/play-all-dark.svg new file mode 100644 index 00000000000..a8ff8f57595 --- /dev/null +++ b/apps/vscode-wing/resources/play-all-dark.svg @@ -0,0 +1,10 @@ + diff --git a/apps/vscode-wing/resources/play-all-light.svg b/apps/vscode-wing/resources/play-all-light.svg new file mode 100644 index 00000000000..b2883623d14 --- /dev/null +++ b/apps/vscode-wing/resources/play-all-light.svg @@ -0,0 +1,10 @@ + diff --git a/apps/vscode-wing/resources/play-dark.svg b/apps/vscode-wing/resources/play-dark.svg new file mode 100644 index 00000000000..6e208f956e7 --- /dev/null +++ b/apps/vscode-wing/resources/play-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/vscode-wing/resources/play-light.svg b/apps/vscode-wing/resources/play-light.svg new file mode 100644 index 00000000000..032a2f0a2c5 --- /dev/null +++ b/apps/vscode-wing/resources/play-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/vscode-wing/resources/resource-icons/dark/archive-box.svg b/apps/vscode-wing/resources/resource-icons/dark/archive-box.svg new file mode 100644 index 00000000000..f6ef06fab73 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/dark/archive-box.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/dark/beaker.svg b/apps/vscode-wing/resources/resource-icons/dark/beaker.svg new file mode 100644 index 00000000000..c45c483c366 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/dark/beaker.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/dark/bolt.svg b/apps/vscode-wing/resources/resource-icons/dark/bolt.svg new file mode 100644 index 00000000000..61574e4aee3 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/dark/bolt.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/dark/calculator.svg b/apps/vscode-wing/resources/resource-icons/dark/calculator.svg new file mode 100644 index 00000000000..d79e9726463 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/dark/calculator.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/dark/clock.svg b/apps/vscode-wing/resources/resource-icons/dark/clock.svg new file mode 100644 index 00000000000..87eab2dc2dc --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/dark/clock.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/dark/cloud.svg b/apps/vscode-wing/resources/resource-icons/dark/cloud.svg new file mode 100644 index 00000000000..4eb9e708fbb --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/dark/cloud.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/dark/cube.svg b/apps/vscode-wing/resources/resource-icons/dark/cube.svg new file mode 100644 index 00000000000..8ce57410c00 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/dark/cube.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/dark/globe-alt.svg b/apps/vscode-wing/resources/resource-icons/dark/globe-alt.svg new file mode 100644 index 00000000000..ab078d2abb6 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/dark/globe-alt.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/dark/megaphone.svg b/apps/vscode-wing/resources/resource-icons/dark/megaphone.svg new file mode 100644 index 00000000000..f5c85040fe1 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/dark/megaphone.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/dark/queue-list.svg b/apps/vscode-wing/resources/resource-icons/dark/queue-list.svg new file mode 100644 index 00000000000..7eec9855d33 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/dark/queue-list.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/dark/redis.svg b/apps/vscode-wing/resources/resource-icons/dark/redis.svg new file mode 100644 index 00000000000..66e31bd3336 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/dark/redis.svg @@ -0,0 +1,11 @@ + + + diff --git a/apps/vscode-wing/resources/resource-icons/dark/table-cells.svg b/apps/vscode-wing/resources/resource-icons/dark/table-cells.svg new file mode 100644 index 00000000000..3902cf36eb9 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/dark/table-cells.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/light/archive-box.svg b/apps/vscode-wing/resources/resource-icons/light/archive-box.svg new file mode 100644 index 00000000000..897ecf080e6 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/light/archive-box.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/light/beaker.svg b/apps/vscode-wing/resources/resource-icons/light/beaker.svg new file mode 100644 index 00000000000..6bd47ac6aa2 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/light/beaker.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/light/bolt.svg b/apps/vscode-wing/resources/resource-icons/light/bolt.svg new file mode 100644 index 00000000000..52b44d7d5b1 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/light/bolt.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/light/calculator.svg b/apps/vscode-wing/resources/resource-icons/light/calculator.svg new file mode 100644 index 00000000000..f8c51446893 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/light/calculator.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/light/clock.svg b/apps/vscode-wing/resources/resource-icons/light/clock.svg new file mode 100644 index 00000000000..f9ade369484 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/light/clock.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/light/cloud.svg b/apps/vscode-wing/resources/resource-icons/light/cloud.svg new file mode 100644 index 00000000000..6ca053e0754 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/light/cloud.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/light/cube.svg b/apps/vscode-wing/resources/resource-icons/light/cube.svg new file mode 100644 index 00000000000..7bd64203b32 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/light/cube.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/light/globe-alt.svg b/apps/vscode-wing/resources/resource-icons/light/globe-alt.svg new file mode 100644 index 00000000000..4733d467555 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/light/globe-alt.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/light/megaphone.svg b/apps/vscode-wing/resources/resource-icons/light/megaphone.svg new file mode 100644 index 00000000000..8d077b760b5 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/light/megaphone.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/light/queue-list.svg b/apps/vscode-wing/resources/resource-icons/light/queue-list.svg new file mode 100644 index 00000000000..fc0f71ee8d4 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/light/queue-list.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/resources/resource-icons/light/redis.svg b/apps/vscode-wing/resources/resource-icons/light/redis.svg new file mode 100644 index 00000000000..930dc657380 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/light/redis.svg @@ -0,0 +1,11 @@ + + + diff --git a/apps/vscode-wing/resources/resource-icons/light/table-cells.svg b/apps/vscode-wing/resources/resource-icons/light/table-cells.svg new file mode 100644 index 00000000000..eedec1524f4 --- /dev/null +++ b/apps/vscode-wing/resources/resource-icons/light/table-cells.svg @@ -0,0 +1,3 @@ + diff --git a/apps/vscode-wing/src/bin-helper.ts b/apps/vscode-wing/src/bin-helper.ts index 868fd063e39..b2e89159f33 100644 --- a/apps/vscode-wing/src/bin-helper.ts +++ b/apps/vscode-wing/src/bin-helper.ts @@ -1,4 +1,4 @@ -import { exec } from "child_process"; +import { exec, execSync } from "child_process"; import { env } from "process"; import { StatusBarItem, diff --git a/apps/vscode-wing/src/console.ts b/apps/vscode-wing/src/console.ts deleted file mode 100644 index f3036f4ad17..00000000000 --- a/apps/vscode-wing/src/console.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { spawn } from "child_process"; -import path from "path"; -import { - ExtensionContext, - WebviewPanel, - Uri, - ViewColumn, - window, - workspace, -} from "vscode"; -import { getWingBinAndArgs } from "./bin-helper"; -import { VIEW_TYPE_CONSOLE } from "./constants"; - -export class WingConsoleManager { - consolePanels: Record = {}; - activeConsolePanel: string | undefined; - - constructor(public readonly context: ExtensionContext) {} - - public async openConsole() { - // get the current active file - const editor = window.activeTextEditor; - if (!editor) { - return; - } - const document = editor.document; - if (document.languageId !== "wing") { - return; - } - const uri = document.uri; - - const existingPanel = this.consolePanels[uri.fsPath]; - if (existingPanel) { - existingPanel.reveal(); - return; - } - - const args = await getWingBinAndArgs(this.context); - if (!args) { - return; - } - - const filename = path.basename(uri.fsPath); - let panel: WebviewPanel; - - args.push("it", "--no-open", uri.fsPath); - - const cp = spawn(args[0]!, args.slice(1), { - stdio: ["ignore", "pipe", "pipe"], - windowsHide: true, - shell: false, - }); - - cp.on("error", (err) => { - if (err) { - void window.showErrorMessage(err.message); - - panel?.dispose(); - } - }); - cp.stdout?.once("data", (data) => { - // get localhost url from stdout - const url = data.toString().match(/http:\/\/localhost:\d+/); - if (!url) { - // there should be an error message in a different event - return; - } - - const port = parseInt(url[0].split(":")[2]); - - panel = window.createWebviewPanel( - VIEW_TYPE_CONSOLE, - `${filename} [Console]`, - ViewColumn.Beside, - { - enableScripts: true, - enableCommandUris: true, - portMapping: [{ webviewPort: port, extensionHostPort: port }], - } - ); - panel.iconPath = { - light: Uri.joinPath( - this.context.extensionUri, - "resources", - "icon-light.png" - ), - dark: Uri.joinPath( - this.context.extensionUri, - "resources", - "icon-dark.png" - ), - }; - - this.consolePanels[uri.fsPath] = panel; - this.activeConsolePanel = uri.fsPath; - panel.onDidChangeViewState(() => { - if (panel.active) { - this.activeConsolePanel = uri.fsPath; - } else if (this.activeConsolePanel === uri.fsPath) { - this.activeConsolePanel = undefined; - } - }); - - panel.onDidDispose(() => { - delete this.consolePanels[uri.fsPath]; - this.activeConsolePanel = undefined; - cp.kill(); - }); - panel.webview.html = `\ - - - - - - - - `; - }); - } - - public async openFile() { - if (this.activeConsolePanel) { - const document = await workspace.openTextDocument( - this.activeConsolePanel - ); - - await window.showTextDocument(document); - } - } -} diff --git a/apps/vscode-wing/src/console/console-manager.ts b/apps/vscode-wing/src/console/console-manager.ts new file mode 100644 index 00000000000..6c1856e039f --- /dev/null +++ b/apps/vscode-wing/src/console/console-manager.ts @@ -0,0 +1,274 @@ +import { + window, + WebviewPanel, + commands, + OutputChannel, + TreeView, + ViewColumn, + Uri, + ExtensionContext, +} from "vscode"; + +import { + ResourceItem, + ResourcesExplorerProvider, +} from "./explorer-providers/ResourcesExplorerProvider"; +import { + TestItem, + TestsExplorerProvider, +} from "./explorer-providers/TestsExplorerProvider"; +import { type Client } from "./services/client"; +import { VIEW_TYPE_CONSOLE } from "../constants"; + +export interface ConsoleInstance { + id: string; + wingfile: string; + url: string; + client: Client; + onDidClose: () => void; +} + +export interface ConsoleManager { + addInstance: (instance: ConsoleInstance) => Promise; + getInstance: (instanceId: string) => ConsoleInstance | undefined; + closeInstance: (instanceId: string) => void; + setActiveInstance: (instanceId: string) => Promise; + activeInstances: () => boolean; + getActiveInstanceId: () => string | undefined; +} + +export const createConsoleManager = ( + context: ExtensionContext, + logger: OutputChannel +): ConsoleManager => { + const instances: Record = {}; + const resourcesExplorer = new ResourcesExplorerProvider(); + const testsExplorer = new TestsExplorerProvider(); + + let activeInstanceId: string | undefined; + let webviewPanel: WebviewPanel | undefined; + let explorerView: TreeView | undefined; + let testsExplorerView: TreeView | undefined; + + let timeout: NodeJS.Timeout | undefined; + let logsTimestamp: number = 0; + + const registerCommands = () => { + commands.registerCommand("wingConsole.openResource", async (resourceId) => { + if (!activeInstanceId) { + return; + } + const activePanel = getInstance(activeInstanceId); + if (!activePanel) { + return; + } + await activePanel.client.setSelectedNode(resourceId); + }); + + commands.registerCommand("wingConsole.runTest", async (test: TestItem) => { + if (!activeInstanceId) { + return; + } + const activePanel = getInstance(activeInstanceId); + if (!activePanel) { + return; + } + const tests = testsExplorer.getTests(); + testsExplorer.update( + tests.map((testItem) => { + if (testItem.id === test.id) { + return { + ...test, + status: "running", + }; + } + return testItem; + }) + ); + await activePanel.client.runTest(test.id); + }); + + commands.registerCommand("wingConsole.runAllTests", async () => { + if (!activeInstanceId) { + return; + } + const activePanel = getInstance(activeInstanceId); + if (!activePanel) { + return; + } + const tests = testsExplorer.getTests(); + testsExplorer.update( + tests.map((testItem) => { + return { + ...testItem, + status: "running", + }; + }) + ); + await activePanel.client.runAllTests(); + }); + }; + + const updateLogs = async (instance: ConsoleInstance) => { + const logs = await instance.client.getLogs({ time: logsTimestamp }); + + logs.forEach((log) => { + logger.appendLine(`[${log.level}] ${log.message}`); + }); + logsTimestamp = Date.now(); + }; + + const getTheme = () => { + const theme = window.activeColorTheme.kind; + if (theme === 1) { + return "light"; + } + return "dark"; + }; + + const getInstance = (instanceId: string) => { + if (!instanceId) { + return; + } + return instances[instanceId]; + }; + + const addInstance = async (instance: ConsoleInstance) => { + logger.appendLine(`Wing Console is running at http://${instance.url}`); + + instance.client.onInvalidateQuery({ + onData: async (key) => { + if (activeInstanceId !== instance.id) { + return; + } + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(async () => { + if (!key || key === "app.logs") { + await updateLogs(instance); + } + resourcesExplorer.update(await instance.client.listResources()); + testsExplorer.update(await instance.client.listTests()); + }, 300); + }, + onError: (err) => { + logger.appendLine(err); + }, + }); + instances[instance.id] = instance; + + await setActiveInstance(instance.id); + }; + + const setActiveInstance = async (instanceId: string) => { + const instance = getInstance(instanceId); + if (!instance) { + return; + } + + if (!webviewPanel) { + webviewPanel = window.createWebviewPanel( + VIEW_TYPE_CONSOLE, + `Console`, + ViewColumn.Beside, + { + enableScripts: true, + enableCommandUris: true, + } + ); + + webviewPanel.iconPath = { + light: Uri.joinPath( + context.extensionUri, + "resources", + "icon-light.png" + ), + dark: Uri.joinPath(context.extensionUri, "resources", "icon-dark.png"), + }; + + webviewPanel.onDidDispose(async () => { + resourcesExplorer.clear(); + testsExplorer.clear(); + webviewPanel = undefined; + activeInstanceId = undefined; + + Object.keys(instances).forEach(async (id) => { + await closeInstance(id); + }); + }); + logger.show(); + } + + webviewPanel.title = `${instance.wingfile} - [console]`; + webviewPanel.webview.html = ` + + + + + + + +