diff --git a/package.json b/package.json index 172433dd1..b1b0119fe 100644 --- a/package.json +++ b/package.json @@ -1620,8 +1620,33 @@ }, { "command": "code-for-ibmi.openDebugStatus", - "title": "Toggle hidden files", - "category": "IBM i" + "title": "Open debugger dashboard", + "category": "IBM i", + "icon":"$(dashboard)" + }, + { + "command": "code-for-ibmi.debug.refresh", + "title": "Refresh", + "category": "IBM i", + "icon": "$(refresh)" + }, + { + "command": "code-for-ibmi.debug.job.start", + "title": "Start", + "category": "IBM i", + "icon": "$(debug-start)" + }, + { + "command": "code-for-ibmi.debug.job.restart", + "title": "Restart", + "category": "IBM i", + "icon": "$(debug-restart)" + }, + { + "command": "code-for-ibmi.debug.job.stop", + "title": "Stop", + "category": "IBM i", + "icon": "$(debug-stop)" } ], "keybindings": [ @@ -1691,7 +1716,7 @@ } ] }, - "views": { + "views": { "ibmi-explorer": [ { "id": "helpView", @@ -1742,6 +1767,13 @@ "name": "Results", "when": "code-for-ibmi:searchViewVisible" } + ], + "debug": [ + { + "id": "ibmiDebugBrowser", + "name": "IBM i debugger", + "when": "code-for-ibmi:connected && code-for-ibmi:debug" + } ] }, "submenus": [ @@ -2159,6 +2191,10 @@ { "command": "code-for-ibmi.debug.endDebug", "when": "code-for-ibmi:debug" + }, + { + "command": "code-for-ibmi.debug.refresh", + "when": "never" } ], "view/title": [ @@ -2286,6 +2322,16 @@ "command": "code-for-ibmi.collapseSearchView", "group": "navigation@1", "when": "view == searchView" + }, + { + "command": "code-for-ibmi.openDebugStatus", + "group": "navigation@01", + "when": "view == ibmiDebugBrowser" + }, + { + "command": "code-for-ibmi.debug.refresh", + "group": "navigation@99", + "when": "view == ibmiDebugBrowser" } ], "editor/title": [ @@ -2710,6 +2756,21 @@ "command": "code-for-ibmi.runAction", "when": "view == objectBrowser && (viewItem =~ /^object/ || viewItem == SPF)", "group": "1_workspace@1" + }, + { + "command": "code-for-ibmi.debug.job.start", + "when": "view == ibmiDebugBrowser && viewItem =~ /^debugJob_.*_off$/", + "group": "inline" + }, + { + "command": "code-for-ibmi.debug.job.restart", + "when": "view == ibmiDebugBrowser && viewItem =~ /^debugJob_.*_on$/", + "group": "inline" + }, + { + "command": "code-for-ibmi.debug.job.stop", + "when": "view == ibmiDebugBrowser && viewItem =~ /^debugJob_.*_on$/", + "group": "inline" } ] }, diff --git a/src/api/debug/server.ts b/src/api/debug/server.ts index c1cc4bbb1..2479b7e78 100644 --- a/src/api/debug/server.ts +++ b/src/api/debug/server.ts @@ -84,7 +84,7 @@ export function debugPTFInstalled() { return instance.getConnection()?.remoteFeatures[`startDebugService.sh`] !== undefined; } -export async function isSEPSupported(){ +export async function isSEPSupported() { return (await getDebugServiceDetails()).semanticVersion().major > 1; } @@ -126,7 +126,7 @@ export async function startService(connection: IBMi) { while (!didNotStart && tries < 20) { if (await getDebugServiceJob()) { window.showInformationMessage(t("start.debug.service.succeeded")); - commands.executeCommand("code-for-ibmi.updateConnectedBar"); + refreshDebugSensitiveItems(); return true; } else { @@ -147,7 +147,7 @@ export async function stopService(connection: IBMi) { if (!endResult.code) { window.showInformationMessage(t("stop.debug.service.succeeded")); - commands.executeCommand("code-for-ibmi.updateConnectedBar"); + refreshDebugSensitiveItems(); return true; } else { window.showErrorMessage(t("stop.debug.service.failed", endResult.stdout || endResult.stderr)); @@ -215,7 +215,7 @@ export async function startServer() { return false; } else { - commands.executeCommand("code-for-ibmi.updateConnectedBar"); + refreshDebugSensitiveItems(); window.showInformationMessage(t("strdbgsvr.succeeded")); } } @@ -230,7 +230,7 @@ export async function stopServer() { return false; } else { - commands.executeCommand("code-for-ibmi.updateConnectedBar"); + refreshDebugSensitiveItems(); window.showInformationMessage(t("enddbgsvr.succeeded")); } } @@ -239,4 +239,9 @@ export async function stopServer() { export function getServiceConfigurationFile() { return path.posix.join(binDirectory, "DebugService.env"); +} + +function refreshDebugSensitiveItems() { + commands.executeCommand("code-for-ibmi.updateConnectedBar"); + commands.executeCommand("code-for-ibmi.debug.refresh"); } \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index bdd44af8a..486547ef8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -24,6 +24,7 @@ import { CodeForIBMi, ConnectionData } from "./typings"; import { initializeConnectionBrowser } from "./views/ConnectionBrowser"; import { LibraryListProvider } from "./views/LibraryListView"; import { ProfilesView } from "./views/ProfilesView"; +import { initializeDebugBrowser } from "./views/debugView"; import { HelpView } from "./views/helpView"; import { initializeIFSBrowser } from "./views/ifsBrowser"; import { initializeObjectBrowser } from "./views/objectBrowser"; @@ -46,7 +47,8 @@ export async function activate(context: ExtensionContext): Promise initializeConnectionBrowser(context); initializeObjectBrowser(context) initializeIFSBrowser(context); - + initializeDebugBrowser(context); + context.subscriptions.push( window.registerTreeDataProvider( `helpView`, diff --git a/src/locale/ids/da.ts b/src/locale/ids/da.ts index 38fd9904c..1dad8ae7f 100644 --- a/src/locale/ids/da.ts +++ b/src/locale/ids/da.ts @@ -385,5 +385,7 @@ export const da: Locale = { 'stop.debug.service.succeeded': 'Debug service stopped.', 'stop.debug.service.failed': 'Failed to stop debug service: {0}', 'open.service.configuration': 'Open configuration', - 'detail.reading.error':'Failed to read debug service detail file {0}: {1}' + 'detail.reading.error': 'Failed to read debug service detail file {0}: {1}', + 'start.debug.server.task': 'Starting debug server...', + 'stop.debug.server.task': 'Stopping debug server...' }; \ No newline at end of file diff --git a/src/locale/ids/en.ts b/src/locale/ids/en.ts index 23ee73a54..eaadef488 100644 --- a/src/locale/ids/en.ts +++ b/src/locale/ids/en.ts @@ -385,5 +385,7 @@ export const en: Locale = { 'stop.debug.service.succeeded': 'Debug service stopped.', 'stop.debug.service.failed': 'Failed to stop debug service: {0}', 'open.service.configuration': 'Open configuration', - 'detail.reading.error':'Failed to read debug service detail file {0}: {1}' + 'detail.reading.error': 'Failed to read debug service detail file {0}: {1}', + 'start.debug.server.task': 'Starting debug server...', + 'stop.debug.server.task': 'Stopping debug server...' }; \ No newline at end of file diff --git a/src/locale/ids/fr.ts b/src/locale/ids/fr.ts index 6342de859..10bf051c1 100644 --- a/src/locale/ids/fr.ts +++ b/src/locale/ids/fr.ts @@ -385,5 +385,7 @@ export const fr: Locale = { 'stop.debug.service.succeeded': 'Service de débogage arrêté.', 'stop.debug.service.failed': 'Échec de l\'arrêt du service de débogage: {0}', 'open.service.configuration': 'Ouvrir la configuration', - 'detail.reading.error':'Erreur de lecture du fichier détail du service de débogage {0}: {1}' + 'detail.reading.error': 'Erreur de lecture du fichier détail du service de débogage {0}: {1}', + 'start.debug.server.task': 'Démarrage du serveur de débogage...', + 'stop.debug.server.task': 'Arrêt du serveur de débogage...' }; \ No newline at end of file diff --git a/src/typings.ts b/src/typings.ts index 95bfb0a70..37e12bcdb 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -1,5 +1,5 @@ import { Ignore } from 'ignore'; -import { ProviderResult, Range, ThemeIcon, TreeItem, TreeItemCollapsibleState, WorkspaceFolder } from "vscode"; +import { ProviderResult, Range, ThemeColor, ThemeIcon, TreeItem, TreeItemCollapsibleState, WorkspaceFolder } from "vscode"; import { ConnectionConfiguration } from './api/Configuration'; import { CustomUI } from "./api/CustomUI"; import Instance from "./api/Instance"; @@ -165,6 +165,7 @@ export type FocusOptions = { select?: boolean; focus?: boolean; expand?: boolean export type BrowserItemParameters = { icon?: string + color?: string state?: TreeItemCollapsibleState parent?: BrowserItem } @@ -172,7 +173,7 @@ export type BrowserItemParameters = { export class BrowserItem extends TreeItem { constructor(label: string, readonly params?: BrowserItemParameters) { super(label, params?.state); - this.iconPath = params?.icon ? new ThemeIcon(params.icon) : undefined; + this.iconPath = params?.icon ? new ThemeIcon(params.icon, params.color ? new ThemeColor(params.color) : undefined) : undefined; } get parent() { diff --git a/src/views/debugView.ts b/src/views/debugView.ts new file mode 100644 index 000000000..1d6d21584 --- /dev/null +++ b/src/views/debugView.ts @@ -0,0 +1,105 @@ +import vscode from "vscode"; +import { DebugJob, getDebugServerJob, getDebugServiceDetails, getDebugServiceJob, isDebugEngineRunning, startServer, startService, stopServer, stopService } from "../api/debug/server"; +import { instance } from "../instantiate"; +import { t } from "../locale"; +import { BrowserItem } from "../typings"; + +const title = "IBM i debugger"; + +export function initializeDebugBrowser(context: vscode.ExtensionContext) { + const debugBrowser = new DebugBrowser(); + const debugTreeViewer = vscode.window.createTreeView( + `ibmiDebugBrowser`, { + treeDataProvider: debugBrowser, + showCollapseAll: true + }); + + const updateDebugBrowser = async () => { + if (instance.getConnection()) { + debugTreeViewer.title = `${title} ${(await getDebugServiceDetails()).version}` + debugTreeViewer.description = await isDebugEngineRunning() ? t("online") : t("offline"); + } + else { + debugTreeViewer.title = title; + debugTreeViewer.description = ""; + } + + debugBrowser.refresh(); + } + + instance.onEvent("connected", updateDebugBrowser); + instance.onEvent("disconnected", updateDebugBrowser); + + context.subscriptions.push( + debugTreeViewer, + vscode.commands.registerCommand("code-for-ibmi.debug.refresh", updateDebugBrowser), + vscode.commands.registerCommand("code-for-ibmi.debug.refresh.item", (item: DebugItem) => debugBrowser.refresh(item)), + vscode.commands.registerCommand("code-for-ibmi.debug.job.start", (item: DebugJobItem) => item.start()), + vscode.commands.registerCommand("code-for-ibmi.debug.job.stop", (item: DebugJobItem) => item.stop()), + vscode.commands.registerCommand("code-for-ibmi.debug.job.restart", async (item: DebugJobItem) => await item.start() && item.stop()), + ); +} + +class DebugBrowser implements vscode.TreeDataProvider { + private readonly _emitter: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._emitter.event; + + refresh(item?: DebugItem) { + this._emitter.fire(item); + } + + getTreeItem(element: DebugItem) { + return element; + } + + async getChildren(): Promise { + const connection = instance.getConnection(); + if (connection) { + return [ + new DebugJobItem("server", + t("debug.server"), + startServer, + stopServer, + await getDebugServerJob() + ), + new DebugJobItem("service", + t("debug.service"), + () => startService(connection), + () => stopService(connection), + await getDebugServiceJob() + ) + ]; + } + else { + return []; + } + } +} + +class DebugItem extends BrowserItem { + refresh() { + vscode.commands.executeCommand("code-for-ibmi.debug.refresh.item", this); + } +} + +class DebugJobItem extends DebugItem { + constructor(readonly type: "server" | "service", label: string, readonly startFunction: () => Promise, readonly stopFunction: () => Promise, readonly debugJob?: DebugJob) { + const running = debugJob !== undefined; + super(label, { + state: vscode.TreeItemCollapsibleState.None, + icon: running ? "pass" : "error", + color: running ? "testing.iconPassed" : "testing.iconFailed" + }); + this.contextValue = `debugJob_${type}_${running ? "on" : "off"}`; + this.description = running ? debugJob.name : t("offline"); + this.tooltip = `${t(`listening.on.port${debugJob?.ports.length === 1 ? '' : 's'}`)} ${debugJob?.ports.join(", ")}`; + } + + async start() { + return vscode.window.withProgress({ title: t(`start.debug.${this.type}.task`), location: vscode.ProgressLocation.Window }, this.startFunction); + } + + async stop() { + return vscode.window.withProgress({ title: t(`stop.debug.${this.type}.task`), location: vscode.ProgressLocation.Window }, this.stopFunction); + } +} \ No newline at end of file