Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ 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
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.
36 changes: 21 additions & 15 deletions src/launchSettings/editorProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,22 @@ 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";

const NEW_LAUNCH_JSON = '{\n "version": "0.2.0",\n "configurations": []\n}\n';

const tcfSessions = new Set<vscode.DebugSession>();
/** 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<void>();

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. */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -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) => {
Expand All @@ -360,7 +366,7 @@ function startDebugging(document: vscode.TextDocument, config: LaunchConfigurati
);
})
.finally(() => {
starting = false;
startingAgents.splice(startingAgents.indexOf(agent), 1);
debuggingChanged.fire();
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/launchSettings/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export interface StateMessage {
select?: number;
/** Host paths that do not exist, as "<configuration index>:<JsonPath joined by />". */
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<string, SettingValue>;
}
Expand Down
6 changes: 6 additions & 0 deletions src/launchSettings/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, unknown>): 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;
Expand Down
20 changes: 18 additions & 2 deletions src/launchSettings/webview/launchSettings.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 4 additions & 5 deletions src/launchSettings/webview/launchSettings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { LaunchConfiguration } from "../launchJson";
import type { JsonPath, StateMessage, WebviewMessage } from "../protocol";
import {
agentAddress,
editList,
Field,
FieldKind,
Expand Down Expand Up @@ -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. */
Expand All @@ -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<string, unknown> | null)?.[key]);
Expand Down
9 changes: 9 additions & 0 deletions src/test/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 }));
});