From f8414a6dc733ee9620ee4f5a7f34d95e1561278e Mon Sep 17 00:00:00 2001 From: Andreas Ragnerstam Date: Fri, 6 Nov 2026 09:56:08 +0100 Subject: [PATCH 1/2] Allow starting launch configs on different agents The Start button in TCF Launch Configurations was disabled while any Simics Target Debug session ran, even for a configuration that connects to another host or port. Run and Debug has no such limit. Now a configuration is only blocked when its agent (host:port) already has a running or starting session. Signed-off-by: Andreas Ragnerstam --- CHANGELOG.md | 6 ++++ src/launchSettings/editorProvider.ts | 36 ++++++++++++-------- src/launchSettings/protocol.ts | 4 +-- src/launchSettings/schema.ts | 6 ++++ src/launchSettings/webview/launchSettings.ts | 9 +++-- src/test/schema.test.ts | 9 +++++ 6 files changed, 48 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13cce66..7278914 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to the Simics Target Debug extension will be documented here ## [Unreleased] +### Fixed + +- The Start button in TCF Launch Configurations is no longer disabled while + any Simics Target Debug session runs, only for configurations that connect + to an agent (host and port) that already has a session. + ## [0.5.0] - 2026-11-02 Initial release. diff --git a/src/launchSettings/editorProvider.ts b/src/launchSettings/editorProvider.ts index 2802b9a..e4d0374 100644 --- a/src/launchSettings/editorProvider.ts +++ b/src/launchSettings/editorProvider.ts @@ -14,7 +14,7 @@ import { setValue, } from "./launchJson"; import { SettingValue, StateMessage, WebviewMessage } from "./protocol"; -import { Field, fieldPath, launchFields, launchTemplate, valueAtPath } from "./schema"; +import { Field, agentAddress, fieldPath, launchFields, launchTemplate, valueAtPath } from "./schema"; /** Must match "customEditors" in package.json. */ export const LAUNCH_SETTINGS_VIEW_TYPE = "simics-target-debug.launchSettings"; @@ -22,12 +22,14 @@ export const LAUNCH_SETTINGS_VIEW_TYPE = "simics-target-debug.launchSettings"; const NEW_LAUNCH_JSON = '{\n "version": "0.2.0",\n "configurations": []\n}\n'; const tcfSessions = new Set(); -/** Set from the form's start until VS Code has started the session or failed. */ -let starting = false; +/** Agents started from the form, each kept until VS Code has started the session or failed. */ +const startingAgents: string[] = []; const debuggingChanged = new vscode.EventEmitter(); -function isDebugging(): boolean { - return starting || tcfSessions.size > 0; +/** Agents with a running or starting session, see agentAddress. */ +function busyAgents(): string[] { + const running = [...tcfSessions].map((s) => agentAddress(s.configuration)); + return [...new Set([...running, ...startingAgents])]; } /** The values of the fields stored in VS Code settings, see Field.setting. */ @@ -148,7 +150,7 @@ class LaunchSettingsEditorProvider implements vscode.CustomTextEditorProvider { error: result.error, select, missingPaths: [], - debugging: isDebugging(), + busyAgents: busyAgents(), settings: settingValues(this.fields, document.uri), }; select = undefined; @@ -249,17 +251,20 @@ class LaunchSettingsEditorProvider implements vscode.CustomTextEditorProvider { return; } case "start": { - if (isDebugging()) { + const text = checkedText(document, message.index, message.name); + const config = readConfigurations(text).configurations?.find((c) => c.index === message.index); + if (!config) { + return; + } + const agent = agentAddress(config.values); + if (busyAgents().includes(agent)) { void vscode.window.showInformationMessage( - "A Simics Target Debug session is already running. " + "Stop it before starting another one.", + `A Simics Target Debug session to ${agent} is already running. ` + + "Stop it before starting another one.", ); return; } - const text = checkedText(document, message.index, message.name); - const config = readConfigurations(text).configurations?.find((c) => c.index === message.index); - if (config) { - startDebugging(document, config); - } + startDebugging(document, config); return; } case "add": { @@ -351,7 +356,8 @@ function startDebugging(document: vscode.TextDocument, config: LaunchConfigurati ...config.values, name: config.name, } as vscode.DebugConfiguration; - starting = true; + const agent = agentAddress(config.values); + startingAgents.push(agent); debuggingChanged.fire(); Promise.resolve(vscode.debug.startDebugging(vscode.workspace.getWorkspaceFolder(document.uri), debugConfig)) .catch((err: unknown) => { @@ -360,7 +366,7 @@ function startDebugging(document: vscode.TextDocument, config: LaunchConfigurati ); }) .finally(() => { - starting = false; + startingAgents.splice(startingAgents.indexOf(agent), 1); debuggingChanged.fire(); }); } diff --git a/src/launchSettings/protocol.ts b/src/launchSettings/protocol.ts index 3f8566b..706ec7e 100644 --- a/src/launchSettings/protocol.ts +++ b/src/launchSettings/protocol.ts @@ -12,8 +12,8 @@ export interface StateMessage { select?: number; /** Host paths that do not exist, as ":". */ missingPaths: string[]; - /** A Simics Target Debug session runs or is starting, only one is allowed. */ - debugging: boolean; + /** Agents (see agentAddress) a Simics Target Debug session runs on or is starting on. */ + busyAgents: string[]; /** Values of the fields stored in VS Code settings, by field key. */ settings: Record; } diff --git a/src/launchSettings/schema.ts b/src/launchSettings/schema.ts index fcbd9ef..7daa71d 100644 --- a/src/launchSettings/schema.ts +++ b/src/launchSettings/schema.ts @@ -183,6 +183,12 @@ export function fieldPath(f: Field): string[] { return f.path ?? [f.key]; } +/** host:port of the agent a configuration connects to, with the same defaults as the debug session. */ +export function agentAddress(values: Record): string { + const { host, port } = values; + return `${String(host ?? "localhost")}:${String(port ?? 1534)}`; +} + /** The value at `path` in `values`, if there is one. */ export function valueAtPath(values: unknown, path: readonly (string | number)[]): unknown { let v = values; diff --git a/src/launchSettings/webview/launchSettings.ts b/src/launchSettings/webview/launchSettings.ts index ff5b1c7..037d85c 100644 --- a/src/launchSettings/webview/launchSettings.ts +++ b/src/launchSettings/webview/launchSettings.ts @@ -1,6 +1,7 @@ import type { LaunchConfiguration } from "../launchJson"; import type { JsonPath, StateMessage, WebviewMessage } from "../protocol"; import { + agentAddress, editList, Field, FieldKind, @@ -207,10 +208,8 @@ function renderList(configs: LaunchConfiguration[], active: LaunchConfiguration ); } -/** host:port of the agent, with the same defaults as the debug session. */ function address(config: LaunchConfiguration): string { - const { host, port } = config.values; - return `${String(host ?? "localhost")}:${String(port ?? 1534)}`; + return agentAddress(config.values); } /** Start, Duplicate and Delete for one configuration. */ @@ -237,8 +236,8 @@ function itemActions(config: LaunchConfiguration): HTMLElement { /** Why the configuration cannot be started now, if it cannot. */ function startProblem(config: LaunchConfiguration): string | undefined { - if (state?.debugging) { - return "A Simics Target Debug session is already running"; + if (state?.busyAgents.includes(address(config))) { + return `A Simics Target Debug session to ${address(config)} is already running`; } const empty = (v: unknown) => v === undefined || v === ""; const emptyCell = (row: unknown, key: string) => empty((row as Record | null)?.[key]); diff --git a/src/test/schema.test.ts b/src/test/schema.test.ts index 84dafd4..42a2160 100644 --- a/src/test/schema.test.ts +++ b/src/test/schema.test.ts @@ -3,6 +3,7 @@ import assert from "node:assert/strict"; import * as fs from "fs"; import * as path from "path"; import { + agentAddress, editList, fieldPath, fieldsFromSchema, @@ -307,3 +308,11 @@ test("parseInput converts typed text to values", () => { value: "99999999999999999999", }); }); + +// The form's start button is only blocked for configurations on an agent that +// already has a session, so the address must match the debug session's defaults. +test("agentAddress defaults to the debug session's host and port", () => { + assert.equal(agentAddress({}), "localhost:1534"); + assert.equal(agentAddress({ host: "simhost", port: 1600 }), "simhost:1600"); + assert.notEqual(agentAddress({ port: 1534 }), agentAddress({ port: 1535 })); +}); From 2a5707d954c572312d862635d28d0840c7ba329d Mon Sep 17 00:00:00 2001 From: Andreas Ragnerstam Date: Fri, 6 Nov 2026 10:01:43 +0100 Subject: [PATCH 2/2] Keep launch configuration buttons visible on scroll Start, Duplicate and Delete were at the top of the form, so with a long form you had to scroll up to use them. The headers of the form and the list now stick to the top. Signed-off-by: Andreas Ragnerstam --- CHANGELOG.md | 6 ++++++ src/launchSettings/webview/launchSettings.css | 20 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7278914..d77e6ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to the Simics Target Debug extension will be documented here ## [Unreleased] +### Changed + +- In TCF Launch Configurations, the Start, Duplicate and Delete buttons + above the form, and the Add and Delete All buttons above the list, stay + visible when you scroll. + ### Fixed - The Start button in TCF Launch Configurations is no longer disabled while diff --git a/src/launchSettings/webview/launchSettings.css b/src/launchSettings/webview/launchSettings.css index 4b7d374..0999389 100644 --- a/src/launchSettings/webview/launchSettings.css +++ b/src/launchSettings/webview/launchSettings.css @@ -42,8 +42,12 @@ body { border-right: 1px solid var(--vscode-panel-border, var(--vscode-widget-border, transparent)); } -/* Looks like the title of a VS Code view. */ +/* Looks like the title of a VS Code view. Sticky, so its buttons stay visible in a long list. */ .list-header { + position: sticky; + top: 0; + z-index: 5; + background: var(--vscode-editor-background); display: flex; align-items: center; justify-content: space-between; @@ -129,14 +133,26 @@ body { .details { overflow: auto; padding: 0 24px 24px; + /* Fields that get focus are scrolled into view below the sticky header. */ + scroll-padding-top: 40px; } +/* + * Sticky, so Start, Duplicate and Delete stay visible when the form is scrolled. + * Spans the side padding of .details too, so no field shows beside it. + * Below the drop-down lists (z-index 10), which may open over it. + */ .details-header { + position: sticky; + top: 0; + z-index: 5; + background: var(--vscode-editor-background); display: flex; align-items: center; justify-content: flex-end; gap: 12px; - padding: 10px 14px 0; + margin: 0 -24px; + padding: 10px 38px 4px; } .actions {